1 /*
2 * Copyright (C) 2008 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 * http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17 package com.android.launcher3;
18
19 import android.animation.Animator;
20 import android.animation.AnimatorListenerAdapter;
21 import android.animation.AnimatorSet;
22 import android.animation.ObjectAnimator;
23 import android.animation.PropertyValuesHolder;
24 import android.animation.ValueAnimator;
25 import android.annotation.TargetApi;
26 import android.app.Activity;
27 import android.app.ActivityManager;
28 import android.app.ActivityOptions;
29 import android.app.AlertDialog;
30 import android.app.SearchManager;
31 import android.appwidget.AppWidgetHostView;
32 import android.appwidget.AppWidgetManager;
33 import android.appwidget.AppWidgetProviderInfo;
34 import android.content.ActivityNotFoundException;
35 import android.content.BroadcastReceiver;
36 import android.content.ComponentCallbacks2;
37 import android.content.ComponentName;
38 import android.content.ContentResolver;
39 import android.content.Context;
40 import android.content.DialogInterface;
41 import android.content.Intent;
42 import android.content.IntentFilter;
43 import android.content.IntentSender;
44 import android.content.SharedPreferences;
45 import android.content.pm.ActivityInfo;
46 import android.content.pm.ApplicationInfo;
47 import android.content.pm.PackageManager;
48 import android.content.pm.PackageManager.NameNotFoundException;
49 import android.content.res.Configuration;
50 import android.database.ContentObserver;
51 import android.database.sqlite.SQLiteDatabase;
52 import android.graphics.Bitmap;
53 import android.graphics.Canvas;
54 import android.graphics.Color;
55 import android.graphics.PorterDuff;
56 import android.graphics.Rect;
57 import android.graphics.drawable.Drawable;
58 import android.net.Uri;
59 import android.os.AsyncTask;
60 import android.os.Build;
61 import android.os.Bundle;
62 import android.os.Environment;
63 import android.os.Handler;
64 import android.os.Message;
65 import android.os.StrictMode;
66 import android.os.SystemClock;
67 import android.text.Selection;
68 import android.text.SpannableStringBuilder;
69 import android.text.TextUtils;
70 import android.text.method.TextKeyListener;
71 import android.util.Log;
72 import android.view.Display;
73 import android.view.Gravity;
74 import android.view.HapticFeedbackConstants;
75 import android.view.KeyEvent;
76 import android.view.LayoutInflater;
77 import android.view.Menu;
78 import android.view.MotionEvent;
79 import android.view.Surface;
80 import android.view.View;
81 import android.view.View.OnClickListener;
82 import android.view.View.OnLongClickListener;
83 import android.view.ViewGroup;
84 import android.view.ViewStub;
85 import android.view.ViewTreeObserver;
86 import android.view.Window;
87 import android.view.WindowManager;
88 import android.view.accessibility.AccessibilityEvent;
89 import android.view.inputmethod.InputMethodManager;
90 import android.widget.Advanceable;
91 import android.widget.FrameLayout;
92 import android.widget.ImageView;
93 import android.widget.TextView;
94 import android.widget.Toast;
95
96 import com.android.launcher3.DropTarget.DragObject;
97 import com.android.launcher3.PagedView.PageSwitchListener;
98 import com.android.launcher3.compat.AppWidgetManagerCompat;
99 import com.android.launcher3.compat.LauncherActivityInfoCompat;
100 import com.android.launcher3.compat.LauncherAppsCompat;
101 import com.android.launcher3.compat.PackageInstallerCompat;
102 import com.android.launcher3.compat.PackageInstallerCompat.PackageInstallInfo;
103 import com.android.launcher3.compat.UserHandleCompat;
104 import com.android.launcher3.compat.UserManagerCompat;
105 import com.android.launcher3.util.Thunk;
106 import com.android.launcher3.widget.PendingAddWidgetInfo;
107 import com.android.launcher3.widget.WidgetsContainerView;
108
109 import java.io.DataInputStream;
110 import java.io.DataOutputStream;
111 import java.io.File;
112 import java.io.FileDescriptor;
113 import java.io.FileNotFoundException;
114 import java.io.FileOutputStream;
115 import java.io.IOException;
116 import java.io.PrintWriter;
117 import java.lang.reflect.InvocationTargetException;
118 import java.lang.reflect.Method;
119 import java.text.DateFormat;
120 import java.util.ArrayList;
121 import java.util.Collection;
122 import java.util.Date;
123 import java.util.HashMap;
124 import java.util.HashSet;
125 import java.util.List;
126 import java.util.concurrent.atomic.AtomicInteger;
127
128 /**
129 * Default launcher application.
130 */
131 public class Launcher extends Activity
132 implements View.OnClickListener, OnLongClickListener, LauncherModel.Callbacks,
133 View.OnTouchListener, PageSwitchListener, LauncherProviderChangeListener,
134 LauncherStateTransitionAnimation.Callbacks {
135 static final String TAG = "Launcher";
136 static final boolean LOGD = true;
137
138 static final boolean PROFILE_STARTUP = false;
139 static final boolean DEBUG_WIDGETS = true;
140 static final boolean DEBUG_STRICT_MODE = false;
141 static final boolean DEBUG_RESUME_TIME = false;
142 static final boolean DEBUG_DUMP_LOG = false;
143
144 static final boolean ENABLE_DEBUG_INTENTS = false; // allow DebugIntents to run
145
146 private static final int REQUEST_CREATE_SHORTCUT = 1;
147 private static final int REQUEST_CREATE_APPWIDGET = 5;
148 private static final int REQUEST_PICK_APPWIDGET = 9;
149 private static final int REQUEST_PICK_WALLPAPER = 10;
150
151 private static final int REQUEST_BIND_APPWIDGET = 11;
152 private static final int REQUEST_RECONFIGURE_APPWIDGET = 12;
153
154 /**
155 * IntentStarter uses request codes starting with this. This must be greater than all activity
156 * request codes used internally.
157 */
158 protected static final int REQUEST_LAST = 100;
159
160 static final String EXTRA_SHORTCUT_DUPLICATE = "duplicate";
161
162 static final int SCREEN_COUNT = 5;
163
164 // To turn on these properties, type
165 // adb shell setprop log.tag.PROPERTY_NAME [VERBOSE | SUPPRESS]
166 static final String DUMP_STATE_PROPERTY = "launcher_dump_state";
167
168 // The Intent extra that defines whether to ignore the launch animation
169 static final String INTENT_EXTRA_IGNORE_LAUNCH_ANIMATION =
170 "com.android.launcher3.intent.extra.shortcut.INGORE_LAUNCH_ANIMATION";
171
172 // Type: int
173 private static final String RUNTIME_STATE_CURRENT_SCREEN = "launcher.current_screen";
174 // Type: int
175 private static final String RUNTIME_STATE = "launcher.state";
176 // Type: int
177 private static final String RUNTIME_STATE_PENDING_ADD_CONTAINER = "launcher.add_container";
178 // Type: int
179 private static final String RUNTIME_STATE_PENDING_ADD_SCREEN = "launcher.add_screen";
180 // Type: int
181 private static final String RUNTIME_STATE_PENDING_ADD_CELL_X = "launcher.add_cell_x";
182 // Type: int
183 private static final String RUNTIME_STATE_PENDING_ADD_CELL_Y = "launcher.add_cell_y";
184 // Type: boolean
185 private static final String RUNTIME_STATE_PENDING_FOLDER_RENAME = "launcher.rename_folder";
186 // Type: long
187 private static final String RUNTIME_STATE_PENDING_FOLDER_RENAME_ID = "launcher.rename_folder_id";
188 // Type: int
189 private static final String RUNTIME_STATE_PENDING_ADD_SPAN_X = "launcher.add_span_x";
190 // Type: int
191 private static final String RUNTIME_STATE_PENDING_ADD_SPAN_Y = "launcher.add_span_y";
192 // Type: parcelable
193 private static final String RUNTIME_STATE_PENDING_ADD_WIDGET_INFO = "launcher.add_widget_info";
194 // Type: parcelable
195 private static final String RUNTIME_STATE_PENDING_ADD_WIDGET_ID = "launcher.add_widget_id";
196 // Type: int[]
197 private static final String RUNTIME_STATE_VIEW_IDS = "launcher.view_ids";
198
199 static final String INTRO_SCREEN_DISMISSED = "launcher.intro_screen_dismissed";
200 static final String FIRST_RUN_ACTIVITY_DISPLAYED = "launcher.first_run_activity_displayed";
201
202 static final String FIRST_LOAD_COMPLETE = "launcher.first_load_complete";
203 static final String ACTION_FIRST_LOAD_COMPLETE =
204 "com.android.launcher3.action.FIRST_LOAD_COMPLETE";
205
206 public static final String SHOW_WEIGHT_WATCHER = "debug.show_mem";
207 public static final boolean SHOW_WEIGHT_WATCHER_DEFAULT = false;
208
209 private static final String QSB_WIDGET_ID = "qsb_widget_id";
210 private static final String QSB_WIDGET_PROVIDER = "qsb_widget_provider";
211
212 public static final String USER_HAS_MIGRATED = "launcher.user_migrated_from_old_data";
213
214 /** The different states that Launcher can be in. */
215 enum State { NONE, WORKSPACE, APPS, APPS_SPRING_LOADED, WIDGETS, WIDGETS_SPRING_LOADED };
216 @Thunk State mState = State.WORKSPACE;
217 @Thunk AnimatorSet mStateAnimation;
218 @Thunk LauncherStateTransitionAnimation mStateTransitionAnimation;
219
220 private boolean mIsSafeModeEnabled;
221
222 LauncherOverlayCallbacks mLauncherOverlayCallbacks = new LauncherOverlayCallbacksImpl();
223 LauncherOverlay mLauncherOverlay;
224 InsettableFrameLayout mLauncherOverlayContainer;
225
226 static final int APPWIDGET_HOST_ID = 1024;
227 public static final int EXIT_SPRINGLOADED_MODE_SHORT_TIMEOUT = 300;
228 private static final int ON_ACTIVITY_RESULT_ANIMATION_DELAY = 500;
229 private static final int ACTIVITY_START_DELAY = 1000;
230
231 private HashMap<Integer, Integer> mItemIdToViewId = new HashMap<Integer, Integer>();
232 private static final AtomicInteger sNextGeneratedId = new AtomicInteger(1);
233
234 // How long to wait before the new-shortcut animation automatically pans the workspace
235 private static int NEW_APPS_PAGE_MOVE_DELAY = 500;
236 private static int NEW_APPS_ANIMATION_INACTIVE_TIMEOUT_SECONDS = 5;
237 @Thunk static int NEW_APPS_ANIMATION_DELAY = 500;
238
239 private final BroadcastReceiver mCloseSystemDialogsReceiver
240 = new CloseSystemDialogsIntentReceiver();
241 private final ContentObserver mWidgetObserver = new AppWidgetResetObserver();
242
243 private LayoutInflater mInflater;
244
245 @Thunk Workspace mWorkspace;
246 private View mLauncherView;
247 private View mPageIndicators;
248 @Thunk DragLayer mDragLayer;
249 private DragController mDragController;
250 private View mWeightWatcher;
251
252 private AppWidgetManagerCompat mAppWidgetManager;
253 private LauncherAppWidgetHost mAppWidgetHost;
254
255 @Thunk ItemInfo mPendingAddInfo = new ItemInfo();
256 private LauncherAppWidgetProviderInfo mPendingAddWidgetInfo;
257 private int mPendingAddWidgetId = -1;
258
259 private int[] mTmpAddItemCellCoordinates = new int[2];
260
261 private FolderInfo mFolderInfo;
262
263 private Hotseat mHotseat;
264 private ViewGroup mOverviewPanel;
265
266 private View mAllAppsButton;
267
268 private SearchDropTargetBar mSearchDropTargetBar;
269
270 // Main container view for the all apps screen.
271 @Thunk AppsContainerView mAppsView;
272
273 // Main container view for the widget tray screen.
274 private WidgetsContainerView mWidgetsView;
275
276 private boolean mAutoAdvanceRunning = false;
277 private AppWidgetHostView mQsb;
278
279 private Bundle mSavedState;
280 // We set the state in both onCreate and then onNewIntent in some cases, which causes both
281 // scroll issues (because the workspace may not have been measured yet) and extra work.
282 // Instead, just save the state that we need to restore Launcher to, and commit it in onResume.
283 private State mOnResumeState = State.NONE;
284
285 private SpannableStringBuilder mDefaultKeySsb = null;
286
287 @Thunk boolean mWorkspaceLoading = true;
288
289 private boolean mPaused = true;
290 private boolean mRestoring;
291 private boolean mWaitingForResult;
292 private boolean mOnResumeNeedsLoad;
293
294 private ArrayList<Runnable> mBindOnResumeCallbacks = new ArrayList<Runnable>();
295 private ArrayList<Runnable> mOnResumeCallbacks = new ArrayList<Runnable>();
296
297 private Bundle mSavedInstanceState;
298
299 private LauncherModel mModel;
300 private IconCache mIconCache;
301 @Thunk boolean mUserPresent = true;
302 private boolean mVisible = false;
303 private boolean mHasFocus = false;
304 private boolean mAttached = false;
305
306 @Thunk static LocaleConfiguration sLocaleConfiguration = null;
307
308 private static HashMap<Long, FolderInfo> sFolders = new HashMap<Long, FolderInfo>();
309
310 private View.OnTouchListener mHapticFeedbackTouchListener;
311
312 // Related to the auto-advancing of widgets
313 private final int ADVANCE_MSG = 1;
314 private final int mAdvanceInterval = 20000;
315 private final int mAdvanceStagger = 250;
316 private long mAutoAdvanceSentTime;
317 private long mAutoAdvanceTimeLeft = -1;
318 @Thunk HashMap<View, AppWidgetProviderInfo> mWidgetsToAdvance =
319 new HashMap<View, AppWidgetProviderInfo>();
320
321 // Determines how long to wait after a rotation before restoring the screen orientation to
322 // match the sensor state.
323 private final int mRestoreScreenOrientationDelay = 500;
324
325 @Thunk Drawable mWorkspaceBackgroundDrawable;
326
327 private final ArrayList<Integer> mSynchronouslyBoundPages = new ArrayList<Integer>();
328 private static final boolean DISABLE_SYNCHRONOUS_BINDING_CURRENT_PAGE = false;
329
330 static final ArrayList<String> sDumpLogs = new ArrayList<String>();
331 static Date sDateStamp = new Date();
332 static DateFormat sDateFormat =
333 DateFormat.getDateTimeInstance(DateFormat.SHORT, DateFormat.SHORT);
334 static long sRunStart = System.currentTimeMillis();
335 static final String CORRUPTION_EMAIL_SENT_KEY = "corruptionEmailSent";
336
337 // We only want to get the SharedPreferences once since it does an FS stat each time we get
338 // it from the context.
339 private SharedPreferences mSharedPrefs;
340
341 // Holds the page that we need to animate to, and the icon views that we need to animate up
342 // when we scroll to that page on resume.
343 @Thunk ImageView mFolderIconImageView;
344 private Bitmap mFolderIconBitmap;
345 private Canvas mFolderIconCanvas;
346 private Rect mRectForFolderAnimation = new Rect();
347
348 private BubbleTextView mWaitingForResume;
349
350 protected static HashMap<String, CustomAppWidget> sCustomAppWidgets =
351 new HashMap<String, CustomAppWidget>();
352
353 private static final boolean ENABLE_CUSTOM_WIDGET_TEST = false;
354 static {
355 if (ENABLE_CUSTOM_WIDGET_TEST) {
356 sCustomAppWidgets.put(DummyWidget.class.getName(), new DummyWidget());
357 }
358 }
359
360 // TODO: remove this field and call method directly when Launcher3 can depend on M APIs
361 private static Method sClipRevealMethod = null;
362 static {
363 Class<?> activityOptionsClass = ActivityOptions.class;
364 try {
365 sClipRevealMethod = activityOptionsClass.getDeclaredMethod("makeClipRevealAnimation",
366 View.class, int.class, int.class, int.class, int.class);
367 } catch (Exception e) {
368 // Earlier version
369 }
370 }
371
372 @Thunk Runnable mBuildLayersRunnable = new Runnable() {
373 public void run() {
374 if (mWorkspace != null) {
375 mWorkspace.buildPageHardwareLayers();
376 }
377 }
378 };
379
380 private static PendingAddArguments sPendingAddItem;
381
382 @Thunk static class PendingAddArguments {
383 int requestCode;
384 Intent intent;
385 long container;
386 long screenId;
387 int cellX;
388 int cellY;
389 int appWidgetId;
390 }
391
392 private Stats mStats;
393
394 FocusIndicatorView mFocusHandler;
395
396 @Override
397 protected void onCreate(Bundle savedInstanceState) {
398 if (DEBUG_STRICT_MODE) {
399 StrictMode.setThreadPolicy(new StrictMode.ThreadPolicy.Builder()
400 .detectDiskReads()
401 .detectDiskWrites()
402 .detectNetwork() // or .detectAll() for all detectable problems
403 .penaltyLog()
404 .build());
405 StrictMode.setVmPolicy(new StrictMode.VmPolicy.Builder()
406 .detectLeakedSqlLiteObjects()
407 .detectLeakedClosableObjects()
408 .penaltyLog()
409 .penaltyDeath()
410 .build());
411 }
412
413 if (mLauncherCallbacks != null) {
414 mLauncherCallbacks.preOnCreate();
415 }
416
417 super.onCreate(savedInstanceState);
418
419 LauncherAppState.setApplicationContext(getApplicationContext());
420 LauncherAppState app = LauncherAppState.getInstance();
421 LauncherAppState.getLauncherProvider().setLauncherProviderChangeListener(this);
422
423 // Lazy-initialize the dynamic grid
424 DeviceProfile grid = app.initDynamicGrid(this);
425
426 // the LauncherApplication should call this, but in case of Instrumentation it might not be prese🔵
427 mSharedPrefs = getSharedPreferences(LauncherAppState.getSharedPreferencesKey(),
428 Context.MODE_PRIVATE);
429 mIsSafeModeEnabled = getPackageManager().isSafeMode();
430 mModel = app.setLauncher(this);
431 mIconCache = app.getIconCache();
432 mIconCache.flushInvalidIcons(grid);
433 mDragController = new DragController(this);
434 mInflater = getLayoutInflater();
435 mStateTransitionAnimation = new LauncherStateTransitionAnimation(this, this);
436
437 mStats = new Stats(this);
438
439 mAppWidgetManager = AppWidgetManagerCompat.getInstance(this);
440
441 mAppWidgetHost = new LauncherAppWidgetHost(this, APPWIDGET_HOST_ID);
442 mAppWidgetHost.startListening();
443
444 // If we are getting an onCreate, we can actually preempt onResume and unset mPaused here,
445 // this also ensures that any synchronous binding below doesn't re-trigger another
446 // LauncherModel load.
447 mPaused = false;
448
449 if (PROFILE_STARTUP) {
450 android.os.Debug.startMethodTracing(
451 Environment.getExternalStorageDirectory() + "/launcher");
452 }
453
454 checkForLocaleChange();
455 setContentView(R.layout.launcher);
456
457 setupViews();
458 grid.layout(this);
459
460 registerContentObservers();
461
462 lockAllApps();
463
464 mSavedState = savedInstanceState;
465 restoreState(mSavedState);
466
467 if (PROFILE_STARTUP) {
468 android.os.Debug.stopMethodTracing();
469 }
470
471 if (!mRestoring) {
472 if (DISABLE_SYNCHRONOUS_BINDING_CURRENT_PAGE) {
473 // If the user leaves launcher, then we should just load items asynchronously when
474 // they return.
475 mModel.startLoader(true, PagedView.INVALID_RESTORE_PAGE);
476 } else {
477 // We only load the page synchronously if the user rotates (or triggers a
478 // configuration change) while launcher is in the foreground
479 mModel.startLoader(true, mWorkspace.getRestorePage());
480 }
481 }
482
483 // For handling default keys
484 mDefaultKeySsb = new SpannableStringBuilder();
485 Selection.setSelection(mDefaultKeySsb, 0);
486
487 IntentFilter filter = new IntentFilter(Intent.ACTION_CLOSE_SYSTEM_DIALOGS);
488 registerReceiver(mCloseSystemDialogsReceiver, filter);
489
490 // On large interfaces, we want the screen to auto-rotate based on the current orientation
491 unlockScreenOrientation(true);
492
493 if (mLauncherCallbacks != null) {
494 mLauncherCallbacks.onCreate(savedInstanceState);
495 if (mLauncherCallbacks.hasLauncherOverlay()) {
496 ViewStub stub = (ViewStub) findViewById(R.id.launcher_overlay_stub);
497 mLauncherOverlayContainer = (InsettableFrameLayout) stub.inflate();
498 mLauncherOverlay = mLauncherCallbacks.setLauncherOverlayView(
499 mLauncherOverlayContainer, mLauncherOverlayCallbacks);
500 mWorkspace.setLauncherOverlay(mLauncherOverlay);
501 }
502 }
503
504 if (shouldShowIntroScreen()) {
505 showIntroScreen();
506 } else {
507 showFirstRunActivity();
508 showFirstRunClings();
509 }
510 }
511
512 private LauncherCallbacks mLauncherCallbacks;
513
514 public void onPostCreate(Bundle savedInstanceState) {
515 super.onPostCreate(savedInstanceState);
516 if (mLauncherCallbacks != null) {
517 mLauncherCallbacks.onPostCreate(savedInstanceState);
518 }
519 }
520
521 public boolean setLauncherCallbacks(LauncherCallbacks callbacks) {
522 mLauncherCallbacks = callbacks;
523 mLauncherCallbacks.setLauncherAppsCallback(new Launcher.LauncherAppsCallbacks() {
524 @Override
525 public void onAllAppsBoundsChanged(Rect bounds) {
526 mAppsView.setFixedBounds(Launcher.this, bounds);
527 }
528
529 @Override
530 public void dismissAllApps() {
531 showWorkspace(true);
532 }
533 });
534 return true;
535 }
536
537 @Override
538 public void onLauncherProviderChange() {
539 if (mLauncherCallbacks != null) {
540 mLauncherCallbacks.onLauncherProviderChange();
541 }
542 }
543
544 /** To be overridden by subclasses to hint to Launcher that we have custom content */
545 protected boolean hasCustomContentToLeft() {
546 if (mLauncherCallbacks != null) {
547 return mLauncherCallbacks.hasCustomContentToLeft();
548 }
549 return false;
550 }
551
552 /**
553 * To be overridden by subclasses to populate the custom content container and call
554 * {@link #addToCustomContentPage}. This will only be invoked if
555 * {@link #hasCustomContentToLeft()} is {@code true}.
556 */
557 protected void populateCustomContentContainer() {
558 if (mLauncherCallbacks != null) {
559 mLauncherCallbacks.populateCustomContentContainer();
560 }
561 }
562
563 /**
564 * Invoked by subclasses to signal a change to the {@link #addCustomContentToLeft} value to
565 * ensure the custom content page is added or removed if necessary.
566 */
567 protected void invalidateHasCustomContentToLeft() {
568 if (mWorkspace == null || mWorkspace.getScreenOrder().isEmpty()) {
569 // Not bound yet, wait for bindScreens to be called.
570 return;
571 }
572
573 if (!mWorkspace.hasCustomContent() && hasCustomContentToLeft()) {
574 // Create the custom content page and call the subclass to populate it.
575 mWorkspace.createCustomContentContainer();
576 populateCustomContentContainer();
577 } else if (mWorkspace.hasCustomContent() && !hasCustomContentToLeft()) {
578 mWorkspace.removeCustomContentPage();
579 }
580 }
581
582 @Thunk void checkForLocaleChange() {
583 if (sLocaleConfiguration == null) {
584 new AsyncTask<Void, Void, LocaleConfiguration>() {
585 @Override
586 protected LocaleConfiguration doInBackground(Void... unused) {
587 LocaleConfiguration localeConfiguration = new LocaleConfiguration();
588 readConfiguration(Launcher.this, localeConfiguration);
589 return localeConfiguration;
590 }
591
592 @Override
593 protected void onPostExecute(LocaleConfiguration result) {
594 sLocaleConfiguration = result;
595 checkForLocaleChange(); // recursive, but now with a locale configuration
596 }
597 }.execute();
598 return;
599 }
600
601 final Configuration configuration = getResources().getConfiguration();
602
603 final String previousLocale = sLocaleConfiguration.locale;
604 final String locale = configuration.locale.toString();
605
606 final int previousMcc = sLocaleConfiguration.mcc;
607 final int mcc = configuration.mcc;
608
609 final int previousMnc = sLocaleConfiguration.mnc;
610 final int mnc = configuration.mnc;
611
612 boolean localeChanged = !locale.equals(previousLocale) || mcc != previousMcc || mnc != previousMn🔵
613
614 if (localeChanged) {
615 sLocaleConfiguration.locale = locale;
616 sLocaleConfiguration.mcc = mcc;
617 sLocaleConfiguration.mnc = mnc;
618
619 mIconCache.flush();
620
621 final LocaleConfiguration localeConfiguration = sLocaleConfiguration;
622 new AsyncTask<Void, Void, Void>() {
623 public Void doInBackground(Void ... args) {
624 writeConfiguration(Launcher.this, localeConfiguration);
625 return null;
626 }
627 }.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR, (Void) null);
628 }
629 }
630
631 @Thunk static class LocaleConfiguration {
632 public String locale;
633 public int mcc = -1;
634 public int mnc = -1;
635 }
636
637 @Thunk static void readConfiguration(Context context, LocaleConfiguration configuration) {
638 DataInputStream in = null;
639 try {
640 in = new DataInputStream(context.openFileInput(LauncherFiles.LAUNCHER_PREFERENCES));
641 configuration.locale = in.readUTF();
642 configuration.mcc = in.readInt();
643 configuration.mnc = in.readInt();
644 } catch (FileNotFoundException e) {
645 // Ignore
646 } catch (IOException e) {
647 // Ignore
648 } finally {
649 if (in != null) {
650 try {
651 in.close();
652 } catch (IOException e) {
653 // Ignore
654 }
655 }
656 }
657 }
658
659 @Thunk static void writeConfiguration(Context context, LocaleConfiguration configuration) {
660 DataOutputStream out = null;
661 try {
662 out = new DataOutputStream(context.openFileOutput(
663 LauncherFiles.LAUNCHER_PREFERENCES, MODE_PRIVATE));
664 out.writeUTF(configuration.locale);
665 out.writeInt(configuration.mcc);
666 out.writeInt(configuration.mnc);
667 out.flush();
668 } catch (FileNotFoundException e) {
669 // Ignore
670 } catch (IOException e) {
671 //noinspection ResultOfMethodCallIgnored
672 context.getFileStreamPath(LauncherFiles.LAUNCHER_PREFERENCES).delete();
673 } finally {
674 if (out != null) {
675 try {
676 out.close();
677 } catch (IOException e) {
678 // Ignore
679 }
680 }
681 }
682 }
683
684 public Stats getStats() {
685 return mStats;
686 }
687
688 public LayoutInflater getInflater() {
689 return mInflater;
690 }
691
692 public boolean isDraggingEnabled() {
693 // We prevent dragging when we are loading the workspace as it is possible to pick up a view
694 // that is subsequently removed from the workspace in startBinding().
695 return !mModel.isLoadingWorkspace();
696 }
697
698 @TargetApi(Build.VERSION_CODES.JELLY_BEAN_MR1)
699 public static int generateViewId() {
700 if (Build.VERSION.SDK_INT >= 17) {
701 return View.generateViewId();
702 } else {
703 // View.generateViewId() is not available. The following fallback logic is a copy
704 // of its implementation.
705 for (;;) {
706 final int result = sNextGeneratedId.get();
707 // aapt-generated IDs have the high byte nonzero; clamp to the range under that.
708 int newValue = result + 1;
709 if (newValue > 0x00FFFFFF) newValue = 1; // Roll over to 1, not 0.
710 if (sNextGeneratedId.compareAndSet(result, newValue)) {
711 return result;
712 }
713 }
714 }
715 }
716
717 public int getViewIdForItem(ItemInfo info) {
718 // This cast is safe given the > 2B range for int.
719 int itemId = (int) info.id;
720 if (mItemIdToViewId.containsKey(itemId)) {
721 return mItemIdToViewId.get(itemId);
722 }
723 int viewId = generateViewId();
724 mItemIdToViewId.put(itemId, viewId);
725 return viewId;
726 }
727
728 /**
729 * Returns whether we should delay spring loaded mode -- for shortcuts and widgets that have
730 * a configuration step, this allows the proper animations to run after other transitions.
731 */
732 private long completeAdd(PendingAddArguments args) {
733 long screenId = args.screenId;
734 if (args.container == LauncherSettings.Favorites.CONTAINER_DESKTOP) {
735 // When the screen id represents an actual screen (as opposed to a rank) we make sure
736 // that the drop page actually exists.
737 screenId = ensurePendingDropLayoutExists(args.screenId);
738 }
739
740 switch (args.requestCode) {
741 case REQUEST_CREATE_SHORTCUT:
742 completeAddShortcut(args.intent, args.container, screenId, args.cellX,
743 args.cellY);
744 break;
745 case REQUEST_CREATE_APPWIDGET:
746 completeAddAppWidget(args.appWidgetId, args.container, screenId, null, null);
747 break;
748 case REQUEST_RECONFIGURE_APPWIDGET:
749 completeRestoreAppWidget(args.appWidgetId);
750 break;
751 }
752 // Before adding this resetAddInfo(), after a shortcut was added to a workspace screen,
753 // if you turned the screen off and then back while in All Apps, Launcher would not
754 // return to the workspace. Clearing mAddInfo.container here fixes this issue
755 resetAddInfo();
756 return screenId;
757 }
758
759 private void handleActivityResult(
760 final int requestCode, final int resultCode, final Intent data) {
761 // Reset the startActivity waiting flag
762 setWaitingForResult(false);
763 final int pendingAddWidgetId = mPendingAddWidgetId;
764 mPendingAddWidgetId = -1;
765
766 Runnable exitSpringLoaded = new Runnable() {
767 @Override
768 public void run() {
769 exitSpringLoadedDragModeDelayed((resultCode != RESULT_CANCELED),
770 EXIT_SPRINGLOADED_MODE_SHORT_TIMEOUT, null);
771 }
772 };
773
774 if (requestCode == REQUEST_BIND_APPWIDGET) {
775 final int appWidgetId = data != null ?
776 data.getIntExtra(AppWidgetManager.EXTRA_APPWIDGET_ID, -1) : -1;
777 if (resultCode == RESULT_CANCELED) {
778 completeTwoStageWidgetDrop(RESULT_CANCELED, appWidgetId);
779 mWorkspace.removeExtraEmptyScreenDelayed(true, exitSpringLoaded,
780 ON_ACTIVITY_RESULT_ANIMATION_DELAY, false);
781 } else if (resultCode == RESULT_OK) {
782 addAppWidgetImpl(appWidgetId, mPendingAddInfo, null,
783 mPendingAddWidgetInfo, ON_ACTIVITY_RESULT_ANIMATION_DELAY);
784 }
785 return;
786 } else if (requestCode == REQUEST_PICK_WALLPAPER) {
787 if (resultCode == RESULT_OK && mWorkspace.isInOverviewMode()) {
788 mWorkspace.exitOverviewMode(false);
789 }
790 return;
791 }
792
793 boolean isWidgetDrop = (requestCode == REQUEST_PICK_APPWIDGET ||
794 requestCode == REQUEST_CREATE_APPWIDGET);
795
796 final boolean workspaceLocked = isWorkspaceLocked();
797 // We have special handling for widgets
798 if (isWidgetDrop) {
799 final int appWidgetId;
800 int widgetId = data != null ? data.getIntExtra(AppWidgetManager.EXTRA_APPWIDGET_ID, -1)
801 : -1;
802 if (widgetId < 0) {
803 appWidgetId = pendingAddWidgetId;
804 } else {
805 appWidgetId = widgetId;
806 }
807
808 final int result;
809 if (appWidgetId < 0 || resultCode == RESULT_CANCELED) {
810 Log.e(TAG, "Error: appWidgetId (EXTRA_APPWIDGET_ID) was not " +
811 "returned from the widget configuration activity.");
812 result = RESULT_CANCELED;
813 completeTwoStageWidgetDrop(result, appWidgetId);
814 final Runnable onComplete = new Runnable() {
815 @Override
816 public void run() {
817 exitSpringLoadedDragModeDelayed(false, 0, null);
818 }
819 };
820 if (workspaceLocked) {
821 // No need to remove the empty screen if we're mid-binding, as the
822 // the bind will not add the empty screen.
823 mWorkspace.postDelayed(onComplete, ON_ACTIVITY_RESULT_ANIMATION_DELAY);
824 } else {
825 mWorkspace.removeExtraEmptyScreenDelayed(true, onComplete,
826 ON_ACTIVITY_RESULT_ANIMATION_DELAY, false);
827 }
828 } else {
829 if (!workspaceLocked) {
830 if (mPendingAddInfo.container == LauncherSettings.Favorites.CONTAINER_DESKTOP) {
831 // When the screen id represents an actual screen (as opposed to a rank)
832 // we make sure that the drop page actually exists.
833 mPendingAddInfo.screenId =
834 ensurePendingDropLayoutExists(mPendingAddInfo.screenId);
835 }
836 final CellLayout dropLayout = mWorkspace.getScreenWithId(mPendingAddInfo.screenId);
837
838 dropLayout.setDropPending(true);
839 final Runnable onComplete = new Runnable() {
840 @Override
841 public void run() {
842 completeTwoStageWidgetDrop(resultCode, appWidgetId);
843 dropLayout.setDropPending(false);
844 }
845 };
846 mWorkspace.removeExtraEmptyScreenDelayed(true, onComplete,
847 ON_ACTIVITY_RESULT_ANIMATION_DELAY, false);
848 } else {
849 PendingAddArguments args = preparePendingAddArgs(requestCode, data, appWidgetId,
850 mPendingAddInfo);
851 sPendingAddItem = args;
852 }
853 }
854 return;
855 }
856
857 if (requestCode == REQUEST_RECONFIGURE_APPWIDGET) {
858 if (resultCode == RESULT_OK) {
859 // Update the widget view.
860 PendingAddArguments args = preparePendingAddArgs(requestCode, data,
861 pendingAddWidgetId, mPendingAddInfo);
862 if (workspaceLocked) {
863 sPendingAddItem = args;
864 } else {
865 completeAdd(args);
866 }
867 }
868 // Leave the widget in the pending state if the user canceled the configure.
869 return;
870 }
871
872 // The pattern used here is that a user PICKs a specific application,
873 // which, depending on the target, might need to CREATE the actual target.
874
875 // For example, the user would PICK_SHORTCUT for "Music playlist", and we
876 // launch over to the Music app to actually CREATE_SHORTCUT.
877 if (resultCode == RESULT_OK && mPendingAddInfo.container != ItemInfo.NO_ID) {
878 final PendingAddArguments args = preparePendingAddArgs(requestCode, data, -1,
879 mPendingAddInfo);
880 if (isWorkspaceLocked()) {
881 sPendingAddItem = args;
882 } else {
883 completeAdd(args);
884 mWorkspace.removeExtraEmptyScreenDelayed(true, exitSpringLoaded,
885 ON_ACTIVITY_RESULT_ANIMATION_DELAY, false);
886 }
887 } else if (resultCode == RESULT_CANCELED) {
888 mWorkspace.removeExtraEmptyScreenDelayed(true, exitSpringLoaded,
889 ON_ACTIVITY_RESULT_ANIMATION_DELAY, false);
890 }
891 mDragLayer.clearAnimatedView();
892
893 }
894
895 @Override
896 protected void onActivityResult(
897 final int requestCode, final int resultCode, final Intent data) {
898 handleActivityResult(requestCode, resultCode, data);
899 if (mLauncherCallbacks != null) {
900 mLauncherCallbacks.onActivityResult(requestCode, resultCode, data);
901 }
902 }
903
904 private PendingAddArguments preparePendingAddArgs(int requestCode, Intent data, int
905 appWidgetId, ItemInfo info) {
906 PendingAddArguments args = new PendingAddArguments();
907 args.requestCode = requestCode;
908 args.intent = data;
909 args.container = info.container;
910 args.screenId = info.screenId;
911 args.cellX = info.cellX;
912 args.cellY = info.cellY;
913 args.appWidgetId = appWidgetId;
914 return args;
915 }
916
917 /**
918 * Check to see if a given screen id exists. If not, create it at the end, return the new id.
919 *
920 * @param screenId the screen id to check
921 * @return the new screen, or screenId if it exists
922 */
923 private long ensurePendingDropLayoutExists(long screenId) {
924 CellLayout dropLayout =
925 (CellLayout) mWorkspace.getScreenWithId(screenId);
926 if (dropLayout == null) {
927 // it's possible that the add screen was removed because it was
928 // empty and a re-bind occurred
929 mWorkspace.addExtraEmptyScreen();
930 return mWorkspace.commitExtraEmptyScreen();
931 } else {
932 return screenId;
933 }
934 }
935
936 @Thunk void completeTwoStageWidgetDrop(final int resultCode, final int appWidgetId) {
937 CellLayout cellLayout =
938 (CellLayout) mWorkspace.getScreenWithId(mPendingAddInfo.screenId);
939 Runnable onCompleteRunnable = null;
940 int animationType = 0;
941
942 AppWidgetHostView boundWidget = null;
943 if (resultCode == RESULT_OK) {
944 animationType = Workspace.COMPLETE_TWO_STAGE_WIDGET_DROP_ANIMATION;
945 final AppWidgetHostView layout = mAppWidgetHost.createView(this, appWidgetId,
946 mPendingAddWidgetInfo);
947 boundWidget = layout;
948 onCompleteRunnable = new Runnable() {
949 @Override
950 public void run() {
951 completeAddAppWidget(appWidgetId, mPendingAddInfo.container,
952 mPendingAddInfo.screenId, layout, null);
953 exitSpringLoadedDragModeDelayed((resultCode != RESULT_CANCELED),
954 EXIT_SPRINGLOADED_MODE_SHORT_TIMEOUT, null);
955 }
956 };
957 } else if (resultCode == RESULT_CANCELED) {
958 mAppWidgetHost.deleteAppWidgetId(appWidgetId);
959 animationType = Workspace.CANCEL_TWO_STAGE_WIDGET_DROP_ANIMATION;
960 }
961 if (mDragLayer.getAnimatedView() != null) {
962 mWorkspace.animateWidgetDrop(mPendingAddInfo, cellLayout,
963 (DragView) mDragLayer.getAnimatedView(), onCompleteRunnable,
964 animationType, boundWidget, true);
965 } else if (onCompleteRunnable != null) {
966 // The animated view may be null in the case of a rotation during widget configuration
967 onCompleteRunnable.run();
968 }
969 }
970
971 @Override
972 protected void onStop() {
973 super.onStop();
974 FirstFrameAnimatorHelper.setIsVisible(false);
975
976 if (mLauncherCallbacks != null) {
977 mLauncherCallbacks.onStop();
978 }
979 }
980
981 @Override
982 protected void onStart() {
983 super.onStart();
984 FirstFrameAnimatorHelper.setIsVisible(true);
985
986 if (mLauncherCallbacks != null) {
987 mLauncherCallbacks.onStart();
988 }
989 }
990
991 @Override
992 protected void onResume() {
993 long startTime = 0;
994 if (DEBUG_RESUME_TIME) {
995 startTime = System.currentTimeMillis();
996 Log.v(TAG, "Launcher.onResume()");
997 }
998
999 if (mLauncherCallbacks != null) {
1000 mLauncherCallbacks.preOnResume();
1001 }
1002
1003 super.onResume();
1004
1005 // Restore the previous launcher state
1006 if (mOnResumeState == State.WORKSPACE) {
1007 showWorkspace(false);
1008 } else if (mOnResumeState == State.APPS) {
1009 showAppsView(false /* animated */, false /* resetListToTop */);
1010 } else if (mOnResumeState == State.WIDGETS) {
1011 showWidgetsView(false, false);
1012 }
1013 mOnResumeState = State.NONE;
1014
1015 // Background was set to gradient in onPause(), restore to black if in all apps.
1016 setWorkspaceBackground(mState == State.WORKSPACE);
1017
1018 mPaused = false;
1019 if (mRestoring || mOnResumeNeedsLoad) {
1020 setWorkspaceLoading(true);
1021 mModel.startLoader(true, PagedView.INVALID_RESTORE_PAGE);
1022 mRestoring = false;
1023 mOnResumeNeedsLoad = false;
1024 }
1025 if (mBindOnResumeCallbacks.size() > 0) {
1026 // We might have postponed some bind calls until onResume (see waitUntilResume) --
1027 // execute them here
1028 long startTimeCallbacks = 0;
1029 if (DEBUG_RESUME_TIME) {
1030 startTimeCallbacks = System.currentTimeMillis();
1031 }
1032
1033 for (int i = 0; i < mBindOnResumeCallbacks.size(); i++) {
1034 mBindOnResumeCallbacks.get(i).run();
1035 }
1036 mBindOnResumeCallbacks.clear();
1037 if (DEBUG_RESUME_TIME) {
1038 Log.d(TAG, "Time spent processing callbacks in onResume: " +
1039 (System.currentTimeMillis() - startTimeCallbacks));
1040 }
1041 }
1042 if (mOnResumeCallbacks.size() > 0) {
1043 for (int i = 0; i < mOnResumeCallbacks.size(); i++) {
1044 mOnResumeCallbacks.get(i).run();
1045 }
1046 mOnResumeCallbacks.clear();
1047 }
1048
1049 // Reset the pressed state of icons that were locked in the press state while activities
1050 // were launching
1051 if (mWaitingForResume != null) {
1052 // Resets the previous workspace icon press state
1053 mWaitingForResume.setStayPressed(false);
1054 }
1055
1056 // It is possible that widgets can receive updates while launcher is not in the foreground.
1057 // Consequently, the widgets will be inflated in the orientation of the foreground activity
1058 // (framework issue). On resuming, we ensure that any widgets are inflated for the current
1059 // orientation.
1060 getWorkspace().reinflateWidgetsIfNecessary();
1061 reinflateQSBIfNecessary();
1062
1063 // Process any items that were added while Launcher was away.
1064 InstallShortcutReceiver.disableAndFlushInstallQueue(this);
1065
1066 if (DEBUG_RESUME_TIME) {
1067 Log.d(TAG, "Time spent in onResume: " + (System.currentTimeMillis() - startTime));
1068 }
1069
1070 if (mWorkspace.getCustomContentCallbacks() != null) {
1071 // If we are resuming and the custom content is the current page, we call onShow().
1072 // It is also poassible that onShow will instead be called slightly after first layout
1073 // if PagedView#setRestorePage was set to the custom content page in onCreate().
1074 if (mWorkspace.isOnOrMovingToCustomContent()) {
1075 mWorkspace.getCustomContentCallbacks().onShow(true);
1076 }
1077 }
1078 mWorkspace.updateInteractionForState();
1079 mWorkspace.onResume();
1080
1081 PackageInstallerCompat.getInstance(this).onResume();
1082
1083 if (mLauncherCallbacks != null) {
1084 mLauncherCallbacks.onResume();
1085 }
1086 }
1087
1088 @Override
1089 protected void onPause() {
1090 // Ensure that items added to Launcher are queued until Launcher returns
1091 InstallShortcutReceiver.enableInstallQueue();
1092 PackageInstallerCompat.getInstance(this).onPause();
1093
1094 super.onPause();
1095 mPaused = true;
1096 mDragController.cancelDrag();
1097 mDragController.resetLastGestureUpTime();
1098
1099 // We call onHide() aggressively. The custom content callbacks should be able to
1100 // debounce excess onHide calls.
1101 if (mWorkspace.getCustomContentCallbacks() != null) {
1102 mWorkspace.getCustomContentCallbacks().onHide();
1103 }
1104
1105 if (mLauncherCallbacks != null) {
1106 mLauncherCallbacks.onPause();
1107 }
1108 }
1109
1110 public interface CustomContentCallbacks {
1111 // Custom content is completely shown. {@code fromResume} indicates whether this was caused
1112 // by a onResume or by scrolling otherwise.
1113 public void onShow(boolean fromResume);
1114
1115 // Custom content is completely hidden
1116 public void onHide();
1117
1118 // Custom content scroll progress changed. From 0 (not showing) to 1 (fully showing).
1119 public void onScrollProgressChanged(float progress);
1120
1121 // Indicates whether the user is allowed to scroll away from the custom content.
1122 boolean isScrollingAllowed();
1123 }
1124
1125 public interface LauncherOverlay {
1126
1127 /**
1128 * Touch interaction leading to overscroll has begun
1129 */
1130 public void onScrollInteractionBegin();
1131
1132 /**
1133 * Touch interaction related to overscroll has ended
1134 */
1135 public void onScrollInteractionEnd();
1136
1137 /**
1138 * Scroll progress, between 0 and 100, when the user scrolls beyond the leftmost
1139 * screen (or in the case of RTL, the rightmost screen).
1140 */
1141 public void onScrollChange(int progress, boolean rtl);
1142
1143 /**
1144 * Screen has stopped scrolling
1145 */
1146 public void onScrollSettled();
1147
1148 /**
1149 * This method can be called by the Launcher in order to force the LauncherOverlay
1150 * to exit fully immersive mode.
1151 */
1152 public void forceExitFullImmersion();
1153 }
1154
1155 public interface LauncherAppsCallbacks {
1156 /**
1157 * Updates launcher to the available space that AllApps can take so as not to overlap with
1158 * any other views.
1159 */
1160 public void onAllAppsBoundsChanged(Rect bounds);
1161
1162 /**
1163 * Called to dismiss all apps if it is showing.
1164 */
1165 public void dismissAllApps();
1166 }
1167
1168 public interface LauncherOverlayCallbacks {
1169 /**
1170 * This method indicates whether a call to {@link #enterFullImmersion()} will succeed,
1171 * however it doesn't modify any state within the launcher.
1172 */
1173 public boolean canEnterFullImmersion();
1174
1175 /**
1176 * Should be called to tell Launcher that the LauncherOverlay will take over interaction,
1177 * eg. by occupying the full screen and handling all touch events.
1178 *
1179 * @return true if Launcher allows the LauncherOverlay to become fully immersive. In this
1180 * case, Launcher will modify any necessary state and assumes the overlay is
1181 * handling all interaction. If false, the LauncherOverlay should cancel any
1182 *
1183 */
1184 public boolean enterFullImmersion();
1185
1186 /**
1187 * Must be called when exiting fully immersive mode. Indicates to Launcher that it has
1188 * full control over UI and state.
1189 */
1190 public void exitFullImmersion();
1191 }
1192
1193 class LauncherOverlayCallbacksImpl implements LauncherOverlayCallbacks {
1194
1195 @Override
1196 public boolean canEnterFullImmersion() {
1197 return mState == State.WORKSPACE;
1198 }
1199
1200 @Override
1201 public boolean enterFullImmersion() {
1202 if (mState == State.WORKSPACE) {
1203 // When fully immersed, disregard any touches which fall through.
1204 mDragLayer.setBlockTouch(true);
1205 return true;
1206 }
1207 return false;
1208 }
1209
1210 @Override
1211 public void exitFullImmersion() {
1212 mDragLayer.setBlockTouch(false);
1213 }
1214 }
1215
1216 protected boolean hasSettings() {
1217 if (mLauncherCallbacks != null) {
1218 return mLauncherCallbacks.hasSettings();
1219 }
1220 return false;
1221 }
1222
1223
1224 public void addToCustomContentPage(View customContent,
1225 CustomContentCallbacks callbacks, String description) {
1226 mWorkspace.addToCustomContentPage(customContent, callbacks, description);
1227 }
1228
1229 // The custom content needs to offset its content to account for the QSB
1230 public int getTopOffsetForCustomContent() {
1231 return mWorkspace.getPaddingTop();
1232 }
1233
1234 @Override
1235 public Object onRetainNonConfigurationInstance() {
1236 // Flag the loader to stop early before switching
1237 if (mModel.isCurrentCallbacks(this)) {
1238 mModel.stopLoader();
1239 }
1240 //TODO(hyunyoungs): stop the widgets loader when there is a rotation.
1241
1242 return Boolean.TRUE;
1243 }
1244
1245 // We can't hide the IME if it was forced open. So don't bother
1246 @Override
1247 public void onWindowFocusChanged(boolean hasFocus) {
1248 super.onWindowFocusChanged(hasFocus);
1249 mHasFocus = hasFocus;
1250
1251 if (mLauncherCallbacks != null) {
1252 mLauncherCallbacks.onWindowFocusChanged(hasFocus);
1253 }
1254 }
1255
1256 private boolean acceptFilter() {
1257 final InputMethodManager inputManager = (InputMethodManager)
1258 getSystemService(Context.INPUT_METHOD_SERVICE);
1259 return !inputManager.isFullscreenMode();
1260 }
1261
1262 @Override
1263 public boolean onKeyDown(int keyCode, KeyEvent event) {
1264 final int uniChar = event.getUnicodeChar();
1265 final boolean handled = super.onKeyDown(keyCode, event);
1266 final boolean isKeyNotWhitespace = uniChar > 0 && !Character.isWhitespace(uniChar);
1267 if (!handled && acceptFilter() && isKeyNotWhitespace) {
1268 boolean gotKey = TextKeyListener.getInstance().onKeyDown(mWorkspace, mDefaultKeySsb,
1269 keyCode, event);
1270 if (gotKey && mDefaultKeySsb != null && mDefaultKeySsb.length() > 0) {
1271 // something usable has been typed - start a search
1272 // the typed text will be retrieved and cleared by
1273 // showSearchDialog()
1274 // If there are multiple keystrokes before the search dialog takes focus,
1275 // onSearchRequested() will be called for every keystroke,
1276 // but it is idempotent, so it's fine.
1277 return onSearchRequested();
1278 }
1279 }
1280
1281 // Eat the long press event so the keyboard doesn't come up.
1282 if (keyCode == KeyEvent.KEYCODE_MENU && event.isLongPress()) {
1283 return true;
1284 }
1285
1286 return handled;
1287 }
1288
1289 private String getTypedText() {
1290 return mDefaultKeySsb.toString();
1291 }
1292
1293 private void clearTypedText() {
1294 mDefaultKeySsb.clear();
1295 mDefaultKeySsb.clearSpans();
1296 Selection.setSelection(mDefaultKeySsb, 0);
1297 }
1298
1299 /**
1300 * Given the integer (ordinal) value of a State enum instance, convert it to a variable of type
1301 * State
1302 */
1303 private static State intToState(int stateOrdinal) {
1304 State state = State.WORKSPACE;
1305 final State[] stateValues = State.values();
1306 for (int i = 0; i < stateValues.length; i++) {
1307 if (stateValues[i].ordinal() == stateOrdinal) {
1308 state = stateValues[i];
1309 break;
1310 }
1311 }
1312 return state;
1313 }
1314
1315 /**
1316 * Restores the previous state, if it exists.
1317 *
1318 * @param savedState The previous state.
1319 */
1320 @SuppressWarnings("unchecked")
1321 private void restoreState(Bundle savedState) {
1322 if (savedState == null) {
1323 return;
1324 }
1325
1326 State state = intToState(savedState.getInt(RUNTIME_STATE, State.WORKSPACE.ordinal()));
1327 if (state == State.APPS || state == State.WIDGETS) {
1328 mOnResumeState = state;
1329 }
1330
1331 int currentScreen = savedState.getInt(RUNTIME_STATE_CURRENT_SCREEN,
1332 PagedView.INVALID_RESTORE_PAGE);
1333 if (currentScreen != PagedView.INVALID_RESTORE_PAGE) {
1334 mWorkspace.setRestorePage(currentScreen);
1335 }
1336
1337 final long pendingAddContainer = savedState.getLong(RUNTIME_STATE_PENDING_ADD_CONTAINER, -1);
1338 final long pendingAddScreen = savedState.getLong(RUNTIME_STATE_PENDING_ADD_SCREEN, -1);
1339
1340 if (pendingAddContainer != ItemInfo.NO_ID && pendingAddScreen > -1) {
1341 mPendingAddInfo.container = pendingAddContainer;
1342 mPendingAddInfo.screenId = pendingAddScreen;
1343 mPendingAddInfo.cellX = savedState.getInt(RUNTIME_STATE_PENDING_ADD_CELL_X);
1344 mPendingAddInfo.cellY = savedState.getInt(RUNTIME_STATE_PENDING_ADD_CELL_Y);
1345 mPendingAddInfo.spanX = savedState.getInt(RUNTIME_STATE_PENDING_ADD_SPAN_X);
1346 mPendingAddInfo.spanY = savedState.getInt(RUNTIME_STATE_PENDING_ADD_SPAN_Y);
1347 AppWidgetProviderInfo info = savedState.getParcelable(
1348 RUNTIME_STATE_PENDING_ADD_WIDGET_INFO);
1349 mPendingAddWidgetInfo = LauncherAppWidgetProviderInfo.fromProviderInfo(this, info);
1350 mPendingAddWidgetId = savedState.getInt(RUNTIME_STATE_PENDING_ADD_WIDGET_ID);
1351 setWaitingForResult(true);
1352 mRestoring = true;
1353 }
1354
1355 boolean renameFolder = savedState.getBoolean(RUNTIME_STATE_PENDING_FOLDER_RENAME, false);
1356 if (renameFolder) {
1357 long id = savedState.getLong(RUNTIME_STATE_PENDING_FOLDER_RENAME_ID);
1358 mFolderInfo = mModel.getFolderById(this, sFolders, id);
1359 mRestoring = true;
1360 }
1361
1362 mItemIdToViewId = (HashMap<Integer, Integer>)
1363 savedState.getSerializable(RUNTIME_STATE_VIEW_IDS);
1364 }
1365
1366 /**
1367 * Finds all the views we need and configure them properly.
1368 */
1369 private void setupViews() {
1370 final DragController dragController = mDragController;
1371
1372 mLauncherView = findViewById(R.id.launcher);
1373 mFocusHandler = (FocusIndicatorView) findViewById(R.id.focus_indicator);
1374 mDragLayer = (DragLayer) findViewById(R.id.drag_layer);
1375 mWorkspace = (Workspace) mDragLayer.findViewById(R.id.workspace);
1376 mWorkspace.setPageSwitchListener(this);
1377 mPageIndicators = mDragLayer.findViewById(R.id.page_indicator);
1378
1379 mLauncherView.setSystemUiVisibility(
1380 View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN | View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION);
1381 mWorkspaceBackgroundDrawable = getResources().getDrawable(R.drawable.workspace_bg);
1382
1383 // Setup the drag layer
1384 mDragLayer.setup(this, dragController);
1385
1386 // Setup the hotseat
1387 mHotseat = (Hotseat) findViewById(R.id.hotseat);
1388 if (mHotseat != null) {
1389 mHotseat.setup(this);
1390 mHotseat.setOnLongClickListener(this);
1391 }
1392
1393 mOverviewPanel = (ViewGroup) findViewById(R.id.overview_panel);
1394 View widgetButton = findViewById(R.id.widget_button);
1395 widgetButton.setOnClickListener(new OnClickListener() {
1396 @Override
1397 public void onClick(View arg0) {
1398 if (!mWorkspace.isSwitchingState()) {
1399 onClickAddWidgetButton(arg0);
1400 }
1401 }
1402 });
1403 widgetButton.setOnTouchListener(getHapticFeedbackTouchListener());
1404
1405 View wallpaperButton = findViewById(R.id.wallpaper_button);
1406 wallpaperButton.setOnClickListener(new OnClickListener() {
1407 @Override
1408 public void onClick(View arg0) {
1409 if (!mWorkspace.isSwitchingState()) {
1410 onClickWallpaperPicker(arg0);
1411 }
1412 }
1413 });
1414 wallpaperButton.setOnTouchListener(getHapticFeedbackTouchListener());
1415
1416 View settingsButton = findViewById(R.id.settings_button);
1417 if (hasSettings()) {
1418 settingsButton.setOnClickListener(new OnClickListener() {
1419 @Override
1420 public void onClick(View arg0) {
1421 if (!mWorkspace.isSwitchingState()) {
1422 onClickSettingsButton(arg0);
1423 }
1424 }
1425 });
1426 settingsButton.setOnTouchListener(getHapticFeedbackTouchListener());
1427 } else {
1428 settingsButton.setVisibility(View.GONE);
1429 }
1430
1431 mOverviewPanel.setAlpha(0f);
1432
1433 // Setup the workspace
1434 mWorkspace.setHapticFeedbackEnabled(false);
1435 mWorkspace.setOnLongClickListener(this);
1436 mWorkspace.setup(dragController);
1437 dragController.addDragListener(mWorkspace);
1438
1439 // Get the search/delete bar
1440 mSearchDropTargetBar = (SearchDropTargetBar)
1441 mDragLayer.findViewById(R.id.search_drop_target_bar);
1442
1443 // Setup Apps
1444 mAppsView = (AppsContainerView) findViewById(R.id.apps_view);
1445 if (mLauncherCallbacks != null && mLauncherCallbacks.overrideAllAppsSearch()) {
1446 mAppsView.hideSearchBar();
1447 }
1448
1449 // Setup AppsCustomize
1450 mWidgetsView = (WidgetsContainerView) findViewById(R.id.widgets_view);
1451
1452 // Setup the drag controller (drop targets have to be added in reverse order in priority)
1453 dragController.setDragScoller(mWorkspace);
1454 dragController.setScrollView(mDragLayer);
1455 dragController.setMoveTarget(mWorkspace);
1456 dragController.addDropTarget(mWorkspace);
1457 if (mSearchDropTargetBar != null) {
1458 mSearchDropTargetBar.setup(this, dragController);
1459 mSearchDropTargetBar.setQsbSearchBar(getOrCreateQsbBar());
1460 }
1461
1462 if (getResources().getBoolean(R.bool.debug_memory_enabled)) {
1463 Log.v(TAG, "adding WeightWatcher");
1464 mWeightWatcher = new WeightWatcher(this);
1465 mWeightWatcher.setAlpha(0.5f);
1466 ((FrameLayout) mLauncherView).addView(mWeightWatcher,
1467 new FrameLayout.LayoutParams(
1468 FrameLayout.LayoutParams.MATCH_PARENT,
1469 FrameLayout.LayoutParams.WRAP_CONTENT,
1470 Gravity.BOTTOM)
1471 );
1472
1473 boolean show = shouldShowWeightWatcher();
1474 mWeightWatcher.setVisibility(show ? View.VISIBLE : View.GONE);
1475 }
1476 }
1477
1478 /**
1479 * Sets the all apps button. This method is called from {@link Hotseat}.
1480 */
1481 public void setAllAppsButton(View allAppsButton) {
1482 mAllAppsButton = allAppsButton;
1483 }
1484
1485 public View getAllAppsButton() {
1486 return mAllAppsButton;
1487 }
1488
1489 /**
1490 * Creates a view representing a shortcut.
1491 *
1492 * @param info The data structure describing the shortcut.
1493 *
1494 * @return A View inflated from R.layout.application.
1495 */
1496 View createShortcut(ShortcutInfo info) {
1497 return createShortcut(R.layout.application,
1498 (ViewGroup) mWorkspace.getChildAt(mWorkspace.getCurrentPage()), info);
1499 }
1500
1501 /**
1502 * Creates a view representing a shortcut inflated from the specified resource.
1503 *
1504 * @param layoutResId The id of the XML layout used to create the shortcut.
1505 * @param parent The group the shortcut belongs to.
1506 * @param info The data structure describing the shortcut.
1507 *
1508 * @return A View inflated from layoutResId.
1509 */
1510 public View createShortcut(int layoutResId, ViewGroup parent, ShortcutInfo info) {
1511 BubbleTextView favorite = (BubbleTextView) mInflater.inflate(layoutResId, parent, false);
1512 favorite.applyFromShortcutInfo(info, mIconCache, true);
1513 favorite.setOnClickListener(this);
1514 favorite.setOnFocusChangeListener(mFocusHandler);
1515 return favorite;
1516 }
1517
1518 /**
1519 * Add a shortcut to the workspace.
1520 *
1521 * @param data The intent describing the shortcut.
1522 * @param cellInfo The position on screen where to create the shortcut.
1523 */
1524 private void completeAddShortcut(Intent data, long container, long screenId, int cellX,
1525 int cellY) {
1526 int[] cellXY = mTmpAddItemCellCoordinates;
1527 int[] touchXY = mPendingAddInfo.dropPos;
1528 CellLayout layout = getCellLayout(container, screenId);
1529
1530 boolean foundCellSpan = false;
1531
1532 ShortcutInfo info = mModel.infoFromShortcutIntent(this, data);
1533 if (info == null) {
1534 return;
1535 }
1536 final View view = createShortcut(info);
1537
1538 // First we check if we already know the exact location where we want to add this item.
1539 if (cellX >= 0 && cellY >= 0) {
1540 cellXY[0] = cellX;
1541 cellXY[1] = cellY;
1542 foundCellSpan = true;
1543
1544 // If appropriate, either create a folder or add to an existing folder
1545 if (mWorkspace.createUserFolderIfNecessary(view, container, layout, cellXY, 0,
1546 true, null,null)) {
1547 return;
1548 }
1549 DragObject dragObject = new DragObject();
1550 dragObject.dragInfo = info;
1551 if (mWorkspace.addToExistingFolderIfNecessary(view, layout, cellXY, 0, dragObject,
1552 true)) {
1553 return;
1554 }
1555 } else if (touchXY != null) {
1556 // when dragging and dropping, just find the closest free spot
1557 int[] result = layout.findNearestVacantArea(touchXY[0], touchXY[1], 1, 1, cellXY);
1558 foundCellSpan = (result != null);
1559 } else {
1560 foundCellSpan = layout.findCellForSpan(cellXY, 1, 1);
1561 }
1562
1563 if (!foundCellSpan) {
1564 showOutOfSpaceMessage(isHotseatLayout(layout));
1565 return;
1566 }
1567
1568 LauncherModel.addItemToDatabase(this, info, container, screenId, cellXY[0], cellXY[1]);
1569
1570 if (!mRestoring) {
1571 mWorkspace.addInScreen(view, container, screenId, cellXY[0], cellXY[1], 1, 1,
1572 isWorkspaceLocked());
1573 }
1574 }
1575
1576 static int[] getSpanForWidget(Context context, ComponentName component, int minWidth,
1577 int minHeight) {
1578 Rect padding = AppWidgetHostView.getDefaultPaddingForWidget(context, component, null);
1579 // We want to account for the extra amount of padding that we are adding to the widget
1580 // to ensure that it gets the full amount of space that it has requested
1581 int requiredWidth = minWidth + padding.left + padding.right;
1582 int requiredHeight = minHeight + padding.top + padding.bottom;
1583 return CellLayout.rectToCell(requiredWidth, requiredHeight, null);
1584 }
1585
1586 static int[] getSpanForWidget(Context context, AppWidgetProviderInfo info) {
1587 return getSpanForWidget(context, info.provider, info.minWidth, info.minHeight);
1588 }
1589
1590 static int[] getMinSpanForWidget(Context context, AppWidgetProviderInfo info) {
1591 return getSpanForWidget(context, info.provider, info.minResizeWidth, info.minResizeHeight);
1592 }
1593
1594 static int[] getSpanForWidget(Context context, PendingAddWidgetInfo info) {
1595 return getSpanForWidget(context, info.componentName, info.minWidth, info.minHeight);
1596 }
1597
1598 static int[] getMinSpanForWidget(Context context, PendingAddWidgetInfo info) {
1599 return getSpanForWidget(context, info.componentName, info.minResizeWidth,
1600 info.minResizeHeight);
1601 }
1602
1603 /**
1604 * Add a widget to the workspace.
1605 *
1606 * @param appWidgetId The app widget id
1607 */
1608 @Thunk void completeAddAppWidget(int appWidgetId, long container, long screenId,
1609 AppWidgetHostView hostView, LauncherAppWidgetProviderInfo appWidgetInfo) {
1610
1611 ItemInfo info = mPendingAddInfo;
1612 if (appWidgetInfo == null) {
1613 appWidgetInfo = LauncherAppWidgetProviderInfo.fromProviderInfo(this,
1614 mAppWidgetManager.getAppWidgetInfo(appWidgetId));
1615 }
1616
1617 if (appWidgetInfo.isCustomWidget) {
1618 appWidgetId = LauncherAppWidgetInfo.CUSTOM_WIDGET_ID;
1619 }
1620
1621 LauncherAppWidgetInfo launcherInfo;
1622 launcherInfo = new LauncherAppWidgetInfo(appWidgetId, appWidgetInfo.provider);
1623 launcherInfo.spanX = info.spanX;
1624 launcherInfo.spanY = info.spanY;
1625 launcherInfo.minSpanX = info.minSpanX;
1626 launcherInfo.minSpanY = info.minSpanY;
1627 launcherInfo.user = mAppWidgetManager.getUser(appWidgetInfo);
1628
1629 LauncherModel.addItemToDatabase(this, launcherInfo,
1630 container, screenId, info.cellX, info.cellY);
1631
1632 if (!mRestoring) {
1633 if (hostView == null) {
1634 // Perform actual inflation because we're live
1635 launcherInfo.hostView = mAppWidgetHost.createView(this, appWidgetId,
1636 appWidgetInfo);
1637 } else {
1638 // The AppWidgetHostView has already been inflated and instantiated
1639 launcherInfo.hostView = hostView;
1640 }
1641 launcherInfo.hostView.setTag(launcherInfo);
1642 launcherInfo.hostView.setVisibility(View.VISIBLE);
1643 launcherInfo.notifyWidgetSizeChanged(this);
1644
1645 mWorkspace.addInScreen(launcherInfo.hostView, container, screenId, info.cellX,
1646 info.cellY, launcherInfo.spanX, launcherInfo.spanY, isWorkspaceLocked());
1647
1648 addWidgetToAutoAdvanceIfNeeded(launcherInfo.hostView, appWidgetInfo);
1649 }
1650 resetAddInfo();
1651 }
1652
1653 private final BroadcastReceiver mReceiver = new BroadcastReceiver() {
1654 @Override
1655 public void onReceive(Context context, Intent intent) {
1656 final String action = intent.getAction();
1657 if (Intent.ACTION_SCREEN_OFF.equals(action)) {
1658 mUserPresent = false;
1659 mDragLayer.clearAllResizeFrames();
1660 updateAutoAdvanceState();
1661
1662 // Reset AllApps to its initial state only if we are not in the middle of
1663 // processing a multi-step drop
1664 if (mAppsView != null && mWidgetsView != null &&
1665 mPendingAddInfo.container == ItemInfo.NO_ID) {
1666 showWorkspace(false);
1667 }
1668 } else if (Intent.ACTION_USER_PRESENT.equals(action)) {
1669 mUserPresent = true;
1670 updateAutoAdvanceState();
1671 } else if (ENABLE_DEBUG_INTENTS && DebugIntents.DELETE_DATABASE.equals(action)) {
1672 mModel.resetLoadedState(false, true);
1673 mModel.startLoader(false, PagedView.INVALID_RESTORE_PAGE,
1674 LauncherModel.LOADER_FLAG_CLEAR_WORKSPACE);
1675 } else if (ENABLE_DEBUG_INTENTS && DebugIntents.MIGRATE_DATABASE.equals(action)) {
1676 mModel.resetLoadedState(false, true);
1677 mModel.startLoader(false, PagedView.INVALID_RESTORE_PAGE,
1678 LauncherModel.LOADER_FLAG_CLEAR_WORKSPACE
1679 | LauncherModel.LOADER_FLAG_MIGRATE_SHORTCUTS);
1680 } else if (LauncherAppsCompat.ACTION_MANAGED_PROFILE_ADDED.equals(action)
1681 || LauncherAppsCompat.ACTION_MANAGED_PROFILE_REMOVED.equals(action)) {
1682 getModel().forceReload();
1683 }
1684 }
1685 };
1686
1687 @Override
1688 public void onAttachedToWindow() {
1689 super.onAttachedToWindow();
1690
1691 // Listen for broadcasts related to user-presence
1692 final IntentFilter filter = new IntentFilter();
1693 filter.addAction(Intent.ACTION_SCREEN_OFF);
1694 filter.addAction(Intent.ACTION_USER_PRESENT);
1695 // For handling managed profiles
1696 filter.addAction(LauncherAppsCompat.ACTION_MANAGED_PROFILE_ADDED);
1697 filter.addAction(LauncherAppsCompat.ACTION_MANAGED_PROFILE_REMOVED);
1698 if (ENABLE_DEBUG_INTENTS) {
1699 filter.addAction(DebugIntents.DELETE_DATABASE);
1700 filter.addAction(DebugIntents.MIGRATE_DATABASE);
1701 }
1702 registerReceiver(mReceiver, filter);
1703 FirstFrameAnimatorHelper.initializeDrawListener(getWindow().getDecorView());
1704 setupTransparentSystemBarsForLmp();
1705 mAttached = true;
1706 mVisible = true;
1707 }
1708
1709 /**
1710 * Sets up transparent navigation and status bars in LMP.
1711 * This method is a no-op for other platform versions.
1712 */
1713 @TargetApi(Build.VERSION_CODES.LOLLIPOP)
1714 private void setupTransparentSystemBarsForLmp() {
1715 if (Utilities.isLmpOrAbove()) {
1716 Window window = getWindow();
1717 window.getAttributes().systemUiVisibility |=
1718 (View.SYSTEM_UI_FLAG_LAYOUT_STABLE
1719 | View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN
1720 | View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION);
1721 window.clearFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS
1722 | WindowManager.LayoutParams.FLAG_TRANSLUCENT_NAVIGATION);
1723 window.addFlags(WindowManager.LayoutParams.FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS);
1724 window.setStatusBarColor(Color.TRANSPARENT);
1725 window.setNavigationBarColor(Color.TRANSPARENT);
1726 }
1727 }
1728
1729 @Override
1730 public void onDetachedFromWindow() {
1731 super.onDetachedFromWindow();
1732 mVisible = false;
1733
1734 if (mAttached) {
1735 unregisterReceiver(mReceiver);
1736 mAttached = false;
1737 }
1738 updateAutoAdvanceState();
1739 }
1740
1741 public void onWindowVisibilityChanged(int visibility) {
1742 mVisible = visibility == View.VISIBLE;
1743 updateAutoAdvanceState();
1744 // The following code used to be in onResume, but it turns out onResume is called when
1745 // you're in All Apps and click home to go to the workspace. onWindowVisibilityChanged
1746 // is a more appropriate event to handle
1747 if (mVisible) {
1748 if (!mWorkspaceLoading) {
1749 final ViewTreeObserver observer = mWorkspace.getViewTreeObserver();
1750 // We want to let Launcher draw itself at least once before we force it to build
1751 // layers on all the workspace pages, so that transitioning to Launcher from other
1752 // apps is nice and speedy.
1753 observer.addOnDrawListener(new ViewTreeObserver.OnDrawListener() {
1754 private boolean mStarted = false;
1755 public void onDraw() {
1756 if (mStarted) return;
1757 mStarted = true;
1758 // We delay the layer building a bit in order to give
1759 // other message processing a time to run. In particular
1760 // this avoids a delay in hiding the IME if it was
1761 // currently shown, because doing that may involve
1762 // some communication back with the app.
1763 mWorkspace.postDelayed(mBuildLayersRunnable, 500);
1764 final ViewTreeObserver.OnDrawListener listener = this;
1765 mWorkspace.post(new Runnable() {
1766 public void run() {
1767 if (mWorkspace != null &&
1768 mWorkspace.getViewTreeObserver() != null) {
1769 mWorkspace.getViewTreeObserver().
1770 removeOnDrawListener(listener);
1771 }
1772 }
1773 });
1774 return;
1775 }
1776 });
1777 }
1778 clearTypedText();
1779 }
1780 }
1781
1782 @Thunk void sendAdvanceMessage(long delay) {
1783 mHandler.removeMessages(ADVANCE_MSG);
1784 Message msg = mHandler.obtainMessage(ADVANCE_MSG);
1785 mHandler.sendMessageDelayed(msg, delay);
1786 mAutoAdvanceSentTime = System.currentTimeMillis();
1787 }
1788
1789 @Thunk void updateAutoAdvanceState() {
1790 boolean autoAdvanceRunning = mVisible && mUserPresent && !mWidgetsToAdvance.isEmpty();
1791 if (autoAdvanceRunning != mAutoAdvanceRunning) {
1792 mAutoAdvanceRunning = autoAdvanceRunning;
1793 if (autoAdvanceRunning) {
1794 long delay = mAutoAdvanceTimeLeft == -1 ? mAdvanceInterval : mAutoAdvanceTimeLeft;
1795 sendAdvanceMessage(delay);
1796 } else {
1797 if (!mWidgetsToAdvance.isEmpty()) {
1798 mAutoAdvanceTimeLeft = Math.max(0, mAdvanceInterval -
1799 (System.currentTimeMillis() - mAutoAdvanceSentTime));
1800 }
1801 mHandler.removeMessages(ADVANCE_MSG);
1802 mHandler.removeMessages(0); // Remove messages sent using postDelayed()
1803 }
1804 }
1805 }
1806
1807 private final Handler mHandler = new Handler() {
1808 @Override
1809 public void handleMessage(Message msg) {
1810 if (msg.what == ADVANCE_MSG) {
1811 int i = 0;
1812 for (View key: mWidgetsToAdvance.keySet()) {
1813 final View v = key.findViewById(mWidgetsToAdvance.get(key).autoAdvanceViewId);
1814 final int delay = mAdvanceStagger * i;
1815 if (v instanceof Advanceable) {
1816 postDelayed(new Runnable() {
1817 public void run() {
1818 ((Advanceable) v).advance();
1819 }
1820 }, delay);
1821 }
1822 i++;
1823 }
1824 sendAdvanceMessage(mAdvanceInterval);
1825 }
1826 }
1827 };
1828
1829 void addWidgetToAutoAdvanceIfNeeded(View hostView, AppWidgetProviderInfo appWidgetInfo) {
1830 if (appWidgetInfo == null || appWidgetInfo.autoAdvanceViewId == -1) return;
1831 View v = hostView.findViewById(appWidgetInfo.autoAdvanceViewId);
1832 if (v instanceof Advanceable) {
1833 mWidgetsToAdvance.put(hostView, appWidgetInfo);
1834 ((Advanceable) v).fyiWillBeAdvancedByHostKThx();
1835 updateAutoAdvanceState();
1836 }
1837 }
1838
1839 void removeWidgetToAutoAdvance(View hostView) {
1840 if (mWidgetsToAdvance.containsKey(hostView)) {
1841 mWidgetsToAdvance.remove(hostView);
1842 updateAutoAdvanceState();
1843 }
1844 }
1845
1846 public void removeAppWidget(LauncherAppWidgetInfo launcherInfo) {
1847 removeWidgetToAutoAdvance(launcherInfo.hostView);
1848 launcherInfo.hostView = null;
1849 }
1850
1851 public void showOutOfSpaceMessage(boolean isHotseatLayout) {
1852 int strId = (isHotseatLayout ? R.string.hotseat_out_of_space : R.string.out_of_space);
1853 Toast.makeText(this, getString(strId), Toast.LENGTH_SHORT).show();
1854 }
1855
1856 public DragLayer getDragLayer() {
1857 return mDragLayer;
1858 }
1859
1860 public AppsContainerView getAppsView() {
1861 return mAppsView;
1862 }
1863
1864 public WidgetsContainerView getWidgetsView() {
1865 return mWidgetsView;
1866 }
1867
1868 public Workspace getWorkspace() {
1869 return mWorkspace;
1870 }
1871
1872 public Hotseat getHotseat() {
1873 return mHotseat;
1874 }
1875
1876 public ViewGroup getOverviewPanel() {
1877 return mOverviewPanel;
1878 }
1879
1880 public SearchDropTargetBar getSearchBar() {
1881 return mSearchDropTargetBar;
1882 }
1883
1884 public LauncherAppWidgetHost getAppWidgetHost() {
1885 return mAppWidgetHost;
1886 }
1887
1888 public LauncherModel getModel() {
1889 return mModel;
1890 }
1891
1892 protected SharedPreferences getSharedPrefs() {
1893 return mSharedPrefs;
1894 }
1895
1896 public void closeSystemDialogs() {
1897 getWindow().closeAllPanels();
1898
1899 // Whatever we were doing is hereby canceled.
1900 setWaitingForResult(false);
1901 }
1902
1903 @Override
1904 protected void onNewIntent(Intent intent) {
1905 long startTime = 0;
1906 if (DEBUG_RESUME_TIME) {
1907 startTime = System.currentTimeMillis();
1908 }
1909 super.onNewIntent(intent);
1910
1911 // Close the menu
1912 if (Intent.ACTION_MAIN.equals(intent.getAction())) {
1913 // also will cancel mWaitingForResult.
1914 closeSystemDialogs();
1915
1916 final boolean alreadyOnHome = mHasFocus && ((intent.getFlags() &
1917 Intent.FLAG_ACTIVITY_BROUGHT_TO_FRONT)
1918 != Intent.FLAG_ACTIVITY_BROUGHT_TO_FRONT);
1919
1920 if (mWorkspace == null) {
1921 // Can be cases where mWorkspace is null, this prevents a NPE
1922 return;
1923 }
1924 Folder openFolder = mWorkspace.getOpenFolder();
1925 // In all these cases, only animate if we're already on home
1926 mWorkspace.exitWidgetResizeMode();
1927
1928 boolean moveToDefaultScreen = mLauncherCallbacks != null ?
1929 mLauncherCallbacks.shouldMoveToDefaultScreenOnHomeIntent() : true;
1930 if (alreadyOnHome && mState == State.WORKSPACE && !mWorkspace.isTouchActive() &&
1931 openFolder == null && moveToDefaultScreen) {
1932 mWorkspace.moveToDefaultScreen(true);
1933 }
1934
1935 closeFolder();
1936 exitSpringLoadedDragMode();
1937
1938 // If we are already on home, then just animate back to the workspace,
1939 // otherwise, just wait until onResume to set the state back to Workspace
1940 if (alreadyOnHome) {
1941 showWorkspace(true);
1942 } else {
1943 mOnResumeState = State.WORKSPACE;
1944 }
1945
1946 final View v = getWindow().peekDecorView();
1947 if (v != null && v.getWindowToken() != null) {
1948 InputMethodManager imm = (InputMethodManager)getSystemService(
1949 INPUT_METHOD_SERVICE);
1950 imm.hideSoftInputFromWindow(v.getWindowToken(), 0);
1951 }
1952
1953 // Reset the apps view
1954 if (!alreadyOnHome && mAppsView != null) {
1955 mAppsView.scrollToTop();
1956 }
1957
1958 // Reset the widgets view
1959 if (!alreadyOnHome && mWidgetsView != null) {
1960 mWidgetsView.scrollToTop();
1961 }
1962
1963 if (mLauncherCallbacks != null) {
1964 mLauncherCallbacks.onHomeIntent();
1965 }
1966 }
1967
1968 if (DEBUG_RESUME_TIME) {
1969 Log.d(TAG, "Time spent in onNewIntent: " + (System.currentTimeMillis() - startTime));
1970 }
1971
1972 if (mLauncherCallbacks != null) {
1973 mLauncherCallbacks.onNewIntent(intent);
1974 }
1975 }
1976
1977 @Override
1978 public void onRestoreInstanceState(Bundle state) {
1979 super.onRestoreInstanceState(state);
1980 for (int page: mSynchronouslyBoundPages) {
1981 mWorkspace.restoreInstanceStateForChild(page);
1982 }
1983 }
1984
1985 @Override
1986 protected void onSaveInstanceState(Bundle outState) {
1987 if (mWorkspace.getChildCount() > 0) {
1988 outState.putInt(RUNTIME_STATE_CURRENT_SCREEN,
1989 mWorkspace.getCurrentPageOffsetFromCustomContent());
1990 }
1991 super.onSaveInstanceState(outState);
1992
1993 outState.putInt(RUNTIME_STATE, mState.ordinal());
1994 // We close any open folder since it will not be re-opened, and we need to make sure
1995 // this state is reflected.
1996 closeFolder();
1997
1998 if (mPendingAddInfo.container != ItemInfo.NO_ID && mPendingAddInfo.screenId > -1 &&
1999 mWaitingForResult) {
2000 outState.putLong(RUNTIME_STATE_PENDING_ADD_CONTAINER, mPendingAddInfo.container);
2001 outState.putLong(RUNTIME_STATE_PENDING_ADD_SCREEN, mPendingAddInfo.screenId);
2002 outState.putInt(RUNTIME_STATE_PENDING_ADD_CELL_X, mPendingAddInfo.cellX);
2003 outState.putInt(RUNTIME_STATE_PENDING_ADD_CELL_Y, mPendingAddInfo.cellY);
2004 outState.putInt(RUNTIME_STATE_PENDING_ADD_SPAN_X, mPendingAddInfo.spanX);
2005 outState.putInt(RUNTIME_STATE_PENDING_ADD_SPAN_Y, mPendingAddInfo.spanY);
2006 outState.putParcelable(RUNTIME_STATE_PENDING_ADD_WIDGET_INFO, mPendingAddWidgetInfo);
2007 outState.putInt(RUNTIME_STATE_PENDING_ADD_WIDGET_ID, mPendingAddWidgetId);
2008 }
2009
2010 if (mFolderInfo != null && mWaitingForResult) {
2011 outState.putBoolean(RUNTIME_STATE_PENDING_FOLDER_RENAME, true);
2012 outState.putLong(RUNTIME_STATE_PENDING_FOLDER_RENAME_ID, mFolderInfo.id);
2013 }
2014
2015 // Save the current widgets tray?
2016 // TODO(hyunyoungs)
2017 outState.putSerializable(RUNTIME_STATE_VIEW_IDS, mItemIdToViewId);
2018
2019 if (mLauncherCallbacks != null) {
2020 mLauncherCallbacks.onSaveInstanceState(outState);
2021 }
2022 }
2023
2024 @Override
2025 public void onDestroy() {
2026 super.onDestroy();
2027
2028 // Remove all pending runnables
2029 mHandler.removeMessages(ADVANCE_MSG);
2030 mHandler.removeMessages(0);
2031 mWorkspace.removeCallbacks(mBuildLayersRunnable);
2032
2033 // Stop callbacks from LauncherModel
2034 LauncherAppState app = (LauncherAppState.getInstance());
2035
2036 // It's possible to receive onDestroy after a new Launcher activity has
2037 // been created. In this case, don't interfere with the new Launcher.
2038 if (mModel.isCurrentCallbacks(this)) {
2039 mModel.stopLoader();
2040 app.setLauncher(null);
2041 }
2042
2043 try {
2044 mAppWidgetHost.stopListening();
2045 } catch (NullPointerException ex) {
2046 Log.w(TAG, "problem while stopping AppWidgetHost during Launcher destruction", ex);
2047 }
2048 mAppWidgetHost = null;
2049
2050 mWidgetsToAdvance.clear();
2051
2052 TextKeyListener.getInstance().release();
2053
2054 // Disconnect any of the callbacks and drawables associated with ItemInfos on the workspace
2055 // to prevent leaking Launcher activities on orientation change.
2056 if (mModel != null) {
2057 mModel.unbindItemInfosAndClearQueuedBindRunnables();
2058 }
2059
2060 getContentResolver().unregisterContentObserver(mWidgetObserver);
2061 unregisterReceiver(mCloseSystemDialogsReceiver);
2062
2063 mDragLayer.clearAllResizeFrames();
2064 ((ViewGroup) mWorkspace.getParent()).removeAllViews();
2065 mWorkspace.removeAllWorkspaceScreens();
2066 mWorkspace = null;
2067 mDragController = null;
2068
2069 LauncherAnimUtils.onDestroyActivity();
2070
2071 if (mLauncherCallbacks != null) {
2072 mLauncherCallbacks.onDestroy();
2073 }
2074 }
2075
2076 public DragController getDragController() {
2077 return mDragController;
2078 }
2079
2080 @Override
2081 public void startActivityForResult(Intent intent, int requestCode) {
2082 onStartForResult(requestCode);
2083 super.startActivityForResult(intent, requestCode);
2084 }
2085
2086 @Override
2087 public void startIntentSenderForResult (IntentSender intent, int requestCode,
2088 Intent fillInIntent, int flagsMask, int flagsValues, int extraFlags, Bundle options) {
2089 onStartForResult(requestCode);
2090 try {
2091 super.startIntentSenderForResult(intent, requestCode,
2092 fillInIntent, flagsMask, flagsValues, extraFlags, options);
2093 } catch (IntentSender.SendIntentException e) {
2094 throw new ActivityNotFoundException();
2095 }
2096 }
2097
2098 private void onStartForResult(int requestCode) {
2099 if (requestCode >= 0) {
2100 setWaitingForResult(true);
2101 }
2102 }
2103
2104 /**
2105 * Indicates that we want global search for this activity by setting the globalSearch
2106 * argument for {@link #startSearch} to true.
2107 */
2108 @Override
2109 public void startSearch(String initialQuery, boolean selectInitialQuery,
2110 Bundle appSearchData, boolean globalSearch) {
2111
2112 showWorkspace(true);
2113
2114 if (initialQuery == null) {
2115 // Use any text typed in the launcher as the initial query
2116 initialQuery = getTypedText();
2117 }
2118 if (appSearchData == null) {
2119 appSearchData = new Bundle();
2120 appSearchData.putString("source", "launcher-search");
2121 }
2122 Rect sourceBounds = new Rect();
2123 if (mSearchDropTargetBar != null) {
2124 sourceBounds = mSearchDropTargetBar.getSearchBarBounds();
2125 }
2126
2127 boolean clearTextImmediately = startSearch(initialQuery, selectInitialQuery,
2128 appSearchData, sourceBounds);
2129 if (clearTextImmediately) {
2130 clearTypedText();
2131 }
2132 }
2133
2134 /**
2135 * Start a text search.
2136 *
2137 * @return {@code true} if the search will start immediately, so any further keypresses
2138 * will be handled directly by the search UI. {@code false} if {@link Launcher} should continue
2139 * to buffer keypresses.
2140 */
2141 public boolean startSearch(String initialQuery,
2142 boolean selectInitialQuery, Bundle appSearchData, Rect sourceBounds) {
2143 if (mLauncherCallbacks != null && mLauncherCallbacks.providesSearch()) {
2144 return mLauncherCallbacks.startSearch(initialQuery, selectInitialQuery, appSearchData,
2145 sourceBounds);
2146 }
2147
2148 startGlobalSearch(initialQuery, selectInitialQuery,
2149 appSearchData, sourceBounds);
2150 return false;
2151 }
2152
2153 /**
2154 * Starts the global search activity. This code is a copied from SearchManager
2155 */
2156 private void startGlobalSearch(String initialQuery,
2157 boolean selectInitialQuery, Bundle appSearchData, Rect sourceBounds) {
2158 final SearchManager searchManager =
2159 (SearchManager) getSystemService(Context.SEARCH_SERVICE);
2160 ComponentName globalSearchActivity = searchManager.getGlobalSearchActivity();
2161 if (globalSearchActivity == null) {
2162 Log.w(TAG, "No global search activity found.");
2163 return;
2164 }
2165 Intent intent = new Intent(SearchManager.INTENT_ACTION_GLOBAL_SEARCH);
2166 intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
2167 intent.setComponent(globalSearchActivity);
2168 // Make sure that we have a Bundle to put source in
2169 if (appSearchData == null) {
2170 appSearchData = new Bundle();
2171 } else {
2172 appSearchData = new Bundle(appSearchData);
2173 }
2174 // Set source to package name of app that starts global search if not set already.
2175 if (!appSearchData.containsKey("source")) {
2176 appSearchData.putString("source", getPackageName());
2177 }
2178 intent.putExtra(SearchManager.APP_DATA, appSearchData);
2179 if (!TextUtils.isEmpty(initialQuery)) {
2180 intent.putExtra(SearchManager.QUERY, initialQuery);
2181 }
2182 if (selectInitialQuery) {
2183 intent.putExtra(SearchManager.EXTRA_SELECT_QUERY, selectInitialQuery);
2184 }
2185 intent.setSourceBounds(sourceBounds);
2186 try {
2187 startActivity(intent);
2188 } catch (ActivityNotFoundException ex) {
2189 Log.e(TAG, "Global search activity not found: " + globalSearchActivity);
2190 }
2191 }
2192
2193 public boolean isOnCustomContent() {
2194 return mWorkspace.isOnOrMovingToCustomContent();
2195 }
2196
2197 @Override
2198 public boolean onPrepareOptionsMenu(Menu menu) {
2199 super.onPrepareOptionsMenu(menu);
2200 if (!isOnCustomContent()) {
2201 // Close any open folders
2202 closeFolder();
2203 // Stop resizing any widgets
2204 mWorkspace.exitWidgetResizeMode();
2205 if (!mWorkspace.isInOverviewMode()) {
2206 // Show the overview mode
2207 showOverviewMode(true);
2208 } else {
2209 showWorkspace(true);
2210 }
2211 }
2212 if (mLauncherCallbacks != null) {
2213 return mLauncherCallbacks.onPrepareOptionsMenu(menu);
2214 }
2215
2216 return false;
2217 }
2218
2219 @Override
2220 public boolean onSearchRequested() {
2221 startSearch(null, false, null, true);
2222 // Use a custom animation for launching search
2223 return true;
2224 }
2225
2226 public boolean isWorkspaceLocked() {
2227 return mWorkspaceLoading || mWaitingForResult;
2228 }
2229
2230 public boolean isWorkspaceLoading() {
2231 return mWorkspaceLoading;
2232 }
2233
2234 private void setWorkspaceLoading(boolean value) {
2235 boolean isLocked = isWorkspaceLocked();
2236 mWorkspaceLoading = value;
2237 if (isLocked != isWorkspaceLocked()) {
2238 onWorkspaceLockedChanged();
2239 }
2240 }
2241
2242 private void setWaitingForResult(boolean value) {
2243 boolean isLocked = isWorkspaceLocked();
2244 mWaitingForResult = value;
2245 if (isLocked != isWorkspaceLocked()) {
2246 onWorkspaceLockedChanged();
2247 }
2248 }
2249
2250 protected void onWorkspaceLockedChanged() {
2251 if (mLauncherCallbacks != null) {
2252 mLauncherCallbacks.onWorkspaceLockedChanged();
2253 }
2254 }
2255
2256 private void resetAddInfo() {
2257 mPendingAddInfo.container = ItemInfo.NO_ID;
2258 mPendingAddInfo.screenId = -1;
2259 mPendingAddInfo.cellX = mPendingAddInfo.cellY = -1;
2260 mPendingAddInfo.spanX = mPendingAddInfo.spanY = -1;
2261 mPendingAddInfo.minSpanX = mPendingAddInfo.minSpanY = -1;
2262 mPendingAddInfo.dropPos = null;
2263 }
2264
2265 void addAppWidgetImpl(final int appWidgetId, final ItemInfo info, final
2266 AppWidgetHostView boundWidget, final LauncherAppWidgetProviderInfo appWidgetInfo) {
2267 addAppWidgetImpl(appWidgetId, info, boundWidget, appWidgetInfo, 0);
2268 }
2269
2270 void addAppWidgetImpl(final int appWidgetId, final ItemInfo info,
2271 final AppWidgetHostView boundWidget, final LauncherAppWidgetProviderInfo appWidgetInfo,
2272 int delay) {
2273 if (appWidgetInfo.configure != null) {
2274 mPendingAddWidgetInfo = appWidgetInfo;
2275 mPendingAddWidgetId = appWidgetId;
2276
2277 // Launch over to configure widget, if needed
2278 mAppWidgetManager.startConfigActivity(appWidgetInfo, appWidgetId, this,
2279 mAppWidgetHost, REQUEST_CREATE_APPWIDGET);
2280
2281 } else {
2282 // Otherwise just add it
2283 Runnable onComplete = new Runnable() {
2284 @Override
2285 public void run() {
2286 // Exit spring loaded mode if necessary after adding the widget
2287 exitSpringLoadedDragModeDelayed(true, EXIT_SPRINGLOADED_MODE_SHORT_TIMEOUT,
2288 null);
2289 }
2290 };
2291 completeAddAppWidget(appWidgetId, info.container, info.screenId, boundWidget,
2292 appWidgetInfo);
2293 mWorkspace.removeExtraEmptyScreenDelayed(true, onComplete, delay, false);
2294 }
2295 }
2296
2297 protected void moveToCustomContentScreen(boolean animate) {
2298 // Close any folders that may be open.
2299 closeFolder();
2300 mWorkspace.moveToCustomContentScreen(animate);
2301 }
2302
2303 public void addPendingItem(PendingAddItemInfo info, long container, long screenId,
2304 int[] cell, int spanX, int spanY) {
2305 switch (info.itemType) {
2306 case LauncherSettings.Favorites.ITEM_TYPE_CUSTOM_APPWIDGET:
2307 case LauncherSettings.Favorites.ITEM_TYPE_APPWIDGET:
2308 int span[] = new int[2];
2309 span[0] = spanX;
2310 span[1] = spanY;
2311 addAppWidgetFromDrop((PendingAddWidgetInfo) info,
2312 container, screenId, cell, span);
2313 break;
2314 case LauncherSettings.Favorites.ITEM_TYPE_SHORTCUT:
2315 processShortcutFromDrop(info.componentName, container, screenId, cell);
2316 break;
2317 default:
2318 throw new IllegalStateException("Unknown item type: " + info.itemType);
2319 }
2320 }
2321
2322 /**
2323 * Process a shortcut drop.
2324 *
2325 * @param componentName The name of the component
2326 * @param screenId The ID of the screen where it should be added
2327 * @param cell The cell it should be added to, optional
2328 */
2329 private void processShortcutFromDrop(ComponentName componentName, long container, long screenId,
2330 int[] cell) {
2331 resetAddInfo();
2332 mPendingAddInfo.container = container;
2333 mPendingAddInfo.screenId = screenId;
2334 mPendingAddInfo.dropPos = null;
2335
2336 if (cell != null) {
2337 mPendingAddInfo.cellX = cell[0];
2338 mPendingAddInfo.cellY = cell[1];
2339 }
2340
2341 Intent createShortcutIntent = new Intent(Intent.ACTION_CREATE_SHORTCUT);
2342 createShortcutIntent.setComponent(componentName);
2343 processShortcut(createShortcutIntent);
2344 }
2345
2346 /**
2347 * Process a widget drop.
2348 *
2349 * @param info The PendingAppWidgetInfo of the widget being added.
2350 * @param screenId The ID of the screen where it should be added
2351 * @param cell The cell it should be added to, optional
2352 */
2353 private void addAppWidgetFromDrop(PendingAddWidgetInfo info, long container, long screenId,
2354 int[] cell, int[] span) {
2355 resetAddInfo();
2356 mPendingAddInfo.container = info.container = container;
2357 mPendingAddInfo.screenId = info.screenId = screenId;
2358 mPendingAddInfo.dropPos = null;
2359 mPendingAddInfo.minSpanX = info.minSpanX;
2360 mPendingAddInfo.minSpanY = info.minSpanY;
2361
2362 if (cell != null) {
2363 mPendingAddInfo.cellX = cell[0];
2364 mPendingAddInfo.cellY = cell[1];
2365 }
2366 if (span != null) {
2367 mPendingAddInfo.spanX = span[0];
2368 mPendingAddInfo.spanY = span[1];
2369 }
2370
2371 AppWidgetHostView hostView = info.boundWidget;
2372 int appWidgetId;
2373 if (hostView != null) {
2374 appWidgetId = hostView.getAppWidgetId();
2375 addAppWidgetImpl(appWidgetId, info, hostView, info.info);
2376 } else {
2377 // In this case, we either need to start an activity to get permission to bind
2378 // the widget, or we need to start an activity to configure the widget, or both.
2379 appWidgetId = getAppWidgetHost().allocateAppWidgetId();
2380 Bundle options = info.bindOptions;
2381
2382 boolean success = mAppWidgetManager.bindAppWidgetIdIfAllowed(
2383 appWidgetId, info.info, options);
2384 if (success) {
2385 addAppWidgetImpl(appWidgetId, info, null, info.info);
2386 } else {
2387 mPendingAddWidgetInfo = info.info;
2388 Intent intent = new Intent(AppWidgetManager.ACTION_APPWIDGET_BIND);
2389 intent.putExtra(AppWidgetManager.EXTRA_APPWIDGET_ID, appWidgetId);
2390 intent.putExtra(AppWidgetManager.EXTRA_APPWIDGET_PROVIDER, info.componentName);
2391 mAppWidgetManager.getUser(mPendingAddWidgetInfo)
2392 .addToIntent(intent, AppWidgetManager.EXTRA_APPWIDGET_PROVIDER_PROFILE);
2393 // TODO: we need to make sure that this accounts for the options bundle.
2394 // intent.putExtra(AppWidgetManager.EXTRA_APPWIDGET_OPTIONS, options);
2395 startActivityForResult(intent, REQUEST_BIND_APPWIDGET);
2396 }
2397 }
2398 }
2399
2400 void processShortcut(Intent intent) {
2401 Utilities.startActivityForResultSafely(this, intent, REQUEST_CREATE_SHORTCUT);
2402 }
2403
2404 void processWallpaper(Intent intent) {
2405 startActivityForResult(intent, REQUEST_PICK_WALLPAPER);
2406 }
2407
2408 FolderIcon addFolder(CellLayout layout, long container, final long screenId, int cellX,
2409 int cellY) {
2410 final FolderInfo folderInfo = new FolderInfo();
2411 folderInfo.title = getText(R.string.folder_name);
2412
2413 // Update the model
2414 LauncherModel.addItemToDatabase(Launcher.this, folderInfo, container, screenId,
2415 cellX, cellY);
2416 sFolders.put(folderInfo.id, folderInfo);
2417
2418 // Create the view
2419 FolderIcon newFolder =
2420 FolderIcon.fromXml(R.layout.folder_icon, this, layout, folderInfo, mIconCache);
2421 mWorkspace.addInScreen(newFolder, container, screenId, cellX, cellY, 1, 1,
2422 isWorkspaceLocked());
2423 // Force measure the new folder icon
2424 CellLayout parent = mWorkspace.getParentCellLayoutForView(newFolder);
2425 parent.getShortcutsAndWidgets().measureChild(newFolder);
2426 return newFolder;
2427 }
2428
2429 void removeFolder(FolderInfo folder) {
2430 sFolders.remove(folder.id);
2431 }
2432
2433 /**
2434 * Registers various content observers. The current implementation registers
2435 * only a favorites observer to keep track of the favorites applications.
2436 */
2437 private void registerContentObservers() {
2438 ContentResolver resolver = getContentResolver();
2439 resolver.registerContentObserver(LauncherProvider.CONTENT_APPWIDGET_RESET_URI,
2440 true, mWidgetObserver);
2441 }
2442
2443 @Override
2444 public boolean dispatchKeyEvent(KeyEvent event) {
2445 if (event.getAction() == KeyEvent.ACTION_DOWN) {
2446 switch (event.getKeyCode()) {
2447 case KeyEvent.KEYCODE_HOME:
2448 return true;
2449 case KeyEvent.KEYCODE_VOLUME_DOWN:
2450 if (Utilities.isPropertyEnabled(DUMP_STATE_PROPERTY)) {
2451 dumpState();
2452 return true;
2453 }
2454 break;
2455 }
2456 } else if (event.getAction() == KeyEvent.ACTION_UP) {
2457 switch (event.getKeyCode()) {
2458 case KeyEvent.KEYCODE_HOME:
2459 return true;
2460 }
2461 }
2462
2463 return super.dispatchKeyEvent(event);
2464 }
2465
2466 @Override
2467 public void onBackPressed() {
2468 if (mLauncherCallbacks != null && mLauncherCallbacks.handleBackPressed()) {
2469 return;
2470 }
2471
2472 LauncherAccessibilityDelegate delegate =
2473 LauncherAppState.getInstance().getAccessibilityDelegate();
2474 if (delegate != null && delegate.onBackPressed()) {
2475 return;
2476 }
2477
2478 if (isAppsViewVisible()) {
2479 showWorkspace(true);
2480 } else if (isWidgetsViewVisible()) {
2481 showOverviewMode(true);
2482 } else if (mWorkspace.isInOverviewMode()) {
2483 mWorkspace.exitOverviewMode(true);
2484 } else if (mWorkspace.getOpenFolder() != null) {
2485 Folder openFolder = mWorkspace.getOpenFolder();
2486 if (openFolder.isEditingName()) {
2487 openFolder.dismissEditingName();
2488 } else {
2489 closeFolder();
2490 }
2491 } else {
2492 mWorkspace.exitWidgetResizeMode();
2493
2494 // Back button is a no-op here, but give at least some feedback for the button press
2495 mWorkspace.showOutlinesTemporarily();
2496 }
2497 }
2498
2499 /**
2500 * Re-listen when widgets are reset.
2501 */
2502 @Thunk void onAppWidgetReset() {
2503 if (mAppWidgetHost != null) {
2504 mAppWidgetHost.startListening();
2505 }
2506 }
2507
2508 /**
2509 * Launches the intent referred by the clicked shortcut.
2510 *
2511 * @param v The view representing the clicked shortcut.
2512 */
2513 public void onClick(View v) {
2514 // Make sure that rogue clicks don't get through while allapps is launching, or after the
2515 // view has detached (it's possible for this to happen if the view is removed mid touch).
2516 if (v.getWindowToken() == null) {
2517 return;
2518 }
2519
2520 if (!mWorkspace.isFinishedSwitchingState()) {
2521 return;
2522 }
2523
2524 if (v instanceof Workspace) {
2525 if (mWorkspace.isInOverviewMode()) {
2526 mWorkspace.exitOverviewMode(true);
2527 }
2528 return;
2529 }
2530
2531 if (v instanceof CellLayout) {
2532 if (mWorkspace.isInOverviewMode()) {
2533 mWorkspace.exitOverviewMode(mWorkspace.indexOfChild(v), true);
2534 }
2535 }
2536
2537 Object tag = v.getTag();
2538 if (tag instanceof ShortcutInfo) {
2539 onClickAppShortcut(v);
2540 } else if (tag instanceof FolderInfo) {
2541 if (v instanceof FolderIcon) {
2542 onClickFolderIcon(v);
2543 }
2544 } else if (v == mAllAppsButton) {
2545 onClickAllAppsButton(v);
2546 } else if (tag instanceof AppInfo) {
2547 startAppShortcutOrInfoActivity(v);
2548 } else if (tag instanceof LauncherAppWidgetInfo) {
2549 if (v instanceof PendingAppWidgetHostView) {
2550 onClickPendingWidget((PendingAppWidgetHostView) v);
2551 }
2552 }
2553 }
2554
2555 public void onClickPagedViewIcon(View v) {
2556 startAppShortcutOrInfoActivity(v);
2557 if (mLauncherCallbacks != null) {
2558 mLauncherCallbacks.onClickPagedViewIcon(v);
2559 }
2560 }
2561
2562 public boolean onTouch(View v, MotionEvent event) {
2563 return false;
2564 }
2565
2566 /**
2567 * Event handler for the app widget view which has not fully restored.
2568 */
2569 public void onClickPendingWidget(final PendingAppWidgetHostView v) {
2570 if (mIsSafeModeEnabled) {
2571 Toast.makeText(this, R.string.safemode_widget_error, Toast.LENGTH_SHORT).show();
2572 return;
2573 }
2574
2575 final LauncherAppWidgetInfo info = (LauncherAppWidgetInfo) v.getTag();
2576 if (v.isReadyForClickSetup()) {
2577 int widgetId = info.appWidgetId;
2578 AppWidgetProviderInfo appWidgetInfo = mAppWidgetManager.getAppWidgetInfo(widgetId);
2579 if (appWidgetInfo != null) {
2580 mPendingAddWidgetInfo = LauncherAppWidgetProviderInfo.fromProviderInfo(
2581 this, appWidgetInfo);
2582 mPendingAddInfo.copyFrom(info);
2583 mPendingAddWidgetId = widgetId;
2584
2585 AppWidgetManagerCompat.getInstance(this).startConfigActivity(appWidgetInfo,
2586 info.appWidgetId, this, mAppWidgetHost, REQUEST_RECONFIGURE_APPWIDGET);
2587 }
2588 } else if (info.installProgress < 0) {
2589 // The install has not been queued
2590 final String packageName = info.providerName.getPackageName();
2591 showBrokenAppInstallDialog(packageName,
2592 new DialogInterface.OnClickListener() {
2593 public void onClick(DialogInterface dialog, int id) {
2594 startActivitySafely(v, LauncherModel.getMarketIntent(packageName), info);
2595 }
2596 });
2597 } else {
2598 // Download has started.
2599 final String packageName = info.providerName.getPackageName();
2600 startActivitySafely(v, LauncherModel.getMarketIntent(packageName), info);
2601 }
2602 }
2603
2604 /**
2605 * Event handler for the "grid" button that appears on the home screen, which
2606 * enters all apps mode.
2607 *
2608 * @param v The view that was clicked.
2609 */
2610 protected void onClickAllAppsButton(View v) {
2611 if (LOGD) Log.d(TAG, "onClickAllAppsButton");
2612 if (isAppsViewVisible()) {
2613 showWorkspace(true);
2614 } else {
2615 showAppsView(true /* animated */, false /* resetListToTop */);
2616 }
2617 }
2618
2619 private void showBrokenAppInstallDialog(final String packageName,
2620 DialogInterface.OnClickListener onSearchClickListener) {
2621 new AlertDialog.Builder(this)
2622 .setTitle(R.string.abandoned_promises_title)
2623 .setMessage(R.string.abandoned_promise_explanation)
2624 .setPositiveButton(R.string.abandoned_search, onSearchClickListener)
2625 .setNeutralButton(R.string.abandoned_clean_this,
2626 new DialogInterface.OnClickListener() {
2627 public void onClick(DialogInterface dialog, int id) {
2628 final UserHandleCompat user = UserHandleCompat.myUserHandle();
2629 mWorkspace.removeAbandonedPromise(packageName, user);
2630 }
2631 })
2632 .create().show();
2633 return;
2634 }
2635
2636 /**
2637 * Event handler for an app shortcut click.
2638 *
2639 * @param v The view that was clicked. Must be a tagged with a {@link ShortcutInfo}.
2640 */
2641 protected void onClickAppShortcut(final View v) {
2642 if (LOGD) Log.d(TAG, "onClickAppShortcut");
2643 Object tag = v.getTag();
2644 if (!(tag instanceof ShortcutInfo)) {
2645 throw new IllegalArgumentException("Input must be a Shortcut");
2646 }
2647
2648 // Open shortcut
2649 final ShortcutInfo shortcut = (ShortcutInfo) tag;
2650
2651 if (shortcut.isDisabled != 0) {
2652 int error = R.string.activity_not_available;
2653 if ((shortcut.isDisabled & ShortcutInfo.FLAG_DISABLED_SAFEMODE) != 0) {
2654 error = R.string.safemode_shortcut_error;
2655 }
2656 Toast.makeText(this, error, Toast.LENGTH_SHORT).show();
2657 return;
2658 }
2659
2660 final Intent intent = shortcut.intent;
2661
2662 // Check for special shortcuts
2663 if (intent.getComponent() != null) {
2664 final String shortcutClass = intent.getComponent().getClassName();
2665
2666 if (shortcutClass.equals(MemoryDumpActivity.class.getName())) {
2667 MemoryDumpActivity.startDump(this);
2668 return;
2669 } else if (shortcutClass.equals(ToggleWeightWatcher.class.getName())) {
2670 toggleShowWeightWatcher();
2671 return;
2672 }
2673 }
2674
2675 // Check for abandoned promise
2676 if ((v instanceof BubbleTextView)
2677 && shortcut.isPromise()
2678 && !shortcut.hasStatusFlag(ShortcutInfo.FLAG_INSTALL_SESSION_ACTIVE)) {
2679 showBrokenAppInstallDialog(
2680 shortcut.getTargetComponent().getPackageName(),
2681 new DialogInterface.OnClickListener() {
2682 public void onClick(DialogInterface dialog, int id) {
2683 startAppShortcutOrInfoActivity(v);
2684 }
2685 });
2686 return;
2687 }
2688
2689 // Start activities
2690 startAppShortcutOrInfoActivity(v);
2691
2692 if (mLauncherCallbacks != null) {
2693 mLauncherCallbacks.onClickAppShortcut(v);
2694 }
2695 }
2696
2697 @Thunk void startAppShortcutOrInfoActivity(View v) {
2698 Object tag = v.getTag();
2699 final ShortcutInfo shortcut;
2700 final Intent intent;
2701 if (tag instanceof ShortcutInfo) {
2702 shortcut = (ShortcutInfo) tag;
2703 intent = shortcut.intent;
2704 int[] pos = new int[2];
2705 v.getLocationOnScreen(pos);
2706 intent.setSourceBounds(new Rect(pos[0], pos[1],
2707 pos[0] + v.getWidth(), pos[1] + v.getHeight()));
2708
2709 } else if (tag instanceof AppInfo) {
2710 shortcut = null;
2711 intent = ((AppInfo) tag).intent;
2712 } else {
2713 throw new IllegalArgumentException("Input must be a Shortcut or AppInfo");
2714 }
2715
2716 boolean success = startActivitySafely(v, intent, tag);
2717 mStats.recordLaunch(intent, shortcut);
2718
2719 if (success && v instanceof BubbleTextView) {
2720 mWaitingForResume = (BubbleTextView) v;
2721 mWaitingForResume.setStayPressed(true);
2722 }
2723 }
2724
2725 /**
2726 * Event handler for a folder icon click.
2727 *
2728 * @param v The view that was clicked. Must be an instance of {@link FolderIcon}.
2729 */
2730 protected void onClickFolderIcon(View v) {
2731 if (LOGD) Log.d(TAG, "onClickFolder");
2732 if (!(v instanceof FolderIcon)){
2733 throw new IllegalArgumentException("Input must be a FolderIcon");
2734 }
2735
2736 FolderIcon folderIcon = (FolderIcon) v;
2737 final FolderInfo info = folderIcon.getFolderInfo();
2738 Folder openFolder = mWorkspace.getFolderForTag(info);
2739
2740 // If the folder info reports that the associated folder is open, then verify that
2741 // it is actually opened. There have been a few instances where this gets out of sync.
2742 if (info.opened && openFolder == null) {
2743 Log.d(TAG, "Folder info marked as open, but associated folder is not open. Screen: "
2744 + info.screenId + " (" + info.cellX + ", " + info.cellY + ")");
2745 info.opened = false;
2746 }
2747
2748 if (!info.opened && !folderIcon.getFolder().isDestroyed()) {
2749 // Close any open folder
2750 closeFolder();
2751 // Open the requested folder
2752 openFolder(folderIcon);
2753 } else {
2754 // Find the open folder...
2755 int folderScreen;
2756 if (openFolder != null) {
2757 folderScreen = mWorkspace.getPageForView(openFolder);
2758 // .. and close it
2759 closeFolder(openFolder);
2760 if (folderScreen != mWorkspace.getCurrentPage()) {
2761 // Close any folder open on the current screen
2762 closeFolder();
2763 // Pull the folder onto this screen
2764 openFolder(folderIcon);
2765 }
2766 }
2767 }
2768
2769 if (mLauncherCallbacks != null) {
2770 mLauncherCallbacks.onClickFolderIcon(v);
2771 }
2772 }
2773
2774 /**
2775 * Event handler for the (Add) Widgets button that appears after a long press
2776 * on the home screen.
2777 */
2778 protected void onClickAddWidgetButton(View view) {
2779 if (LOGD) Log.d(TAG, "onClickAddWidgetButton");
2780 if (mIsSafeModeEnabled) {
2781 Toast.makeText(this, R.string.safemode_widget_error, Toast.LENGTH_SHORT).show();
2782 } else {
2783 showWidgetsView(true /* animated */, true /* resetPageToZero */);
2784 if (mLauncherCallbacks != null) {
2785 mLauncherCallbacks.onClickAddWidgetButton(view);
2786 }
2787 }
2788 }
2789
2790 /**
2791 * Event handler for the wallpaper picker button that appears after a long press
2792 * on the home screen.
2793 */
2794 protected void onClickWallpaperPicker(View v) {
2795 if (LOGD) Log.d(TAG, "onClickWallpaperPicker");
2796 startActivityForResult(new Intent(Intent.ACTION_SET_WALLPAPER).setPackage(getPackageName()),
2797 REQUEST_PICK_WALLPAPER);
2798
2799 if (mLauncherCallbacks != null) {
2800 mLauncherCallbacks.onClickWallpaperPicker(v);
2801 }
2802 }
2803
2804 /**
2805 * Event handler for a click on the settings button that appears after a long press
2806 * on the home screen.
2807 */
2808 protected void onClickSettingsButton(View v) {
2809 if (LOGD) Log.d(TAG, "onClickSettingsButton");
2810 if (mLauncherCallbacks != null) {
2811 mLauncherCallbacks.onClickSettingsButton(v);
2812 }
2813 }
2814
2815 public void onTouchDownAllAppsButton(View v) {
2816 // Provide the same haptic feedback that the system offers for virtual keys.
2817 v.performHapticFeedback(HapticFeedbackConstants.VIRTUAL_KEY);
2818 }
2819
2820 public void performHapticFeedbackOnTouchDown(View v) {
2821 // Provide the same haptic feedback that the system offers for virtual keys.
2822 v.performHapticFeedback(HapticFeedbackConstants.VIRTUAL_KEY);
2823 }
2824
2825 public View.OnTouchListener getHapticFeedbackTouchListener() {
2826 if (mHapticFeedbackTouchListener == null) {
2827 mHapticFeedbackTouchListener = new View.OnTouchListener() {
2828 @Override
2829 public boolean onTouch(View v, MotionEvent event) {
2830 if ((event.getAction() & MotionEvent.ACTION_MASK) == MotionEvent.ACTION_DOWN) {
2831 v.performHapticFeedback(HapticFeedbackConstants.VIRTUAL_KEY);
2832 }
2833 return false;
2834 }
2835 };
2836 }
2837 return mHapticFeedbackTouchListener;
2838 }
2839
2840 public void onDragStarted(View view) {
2841 if (isOnCustomContent()) {
2842 // Custom content screen doesn't participate in drag and drop. If on custom
2843 // content screen, move to default.
2844 moveWorkspaceToDefaultScreen();
2845 }
2846
2847 if (mLauncherCallbacks != null) {
2848 mLauncherCallbacks.onDragStarted(view);
2849 }
2850 }
2851
2852 /**
2853 * Called when the user stops interacting with the launcher.
2854 * This implies that the user is now on the homescreen and is not doing housekeeping.
2855 */
2856 protected void onInteractionEnd() {
2857 if (mLauncherCallbacks != null) {
2858 mLauncherCallbacks.onInteractionEnd();
2859 }
2860 }
2861
2862 /**
2863 * Called when the user starts interacting with the launcher.
2864 * The possible interactions are:
2865 * - open all apps
2866 * - reorder an app shortcut, or a widget
2867 * - open the overview mode.
2868 * This is a good time to stop doing things that only make sense
2869 * when the user is on the homescreen and not doing housekeeping.
2870 */
2871 protected void onInteractionBegin() {
2872 if (mLauncherCallbacks != null) {
2873 mLauncherCallbacks.onInteractionBegin();
2874 }
2875 }
2876
2877 void startApplicationDetailsActivity(ComponentName componentName, UserHandleCompat user) {
2878 try {
2879 LauncherAppsCompat launcherApps = LauncherAppsCompat.getInstance(this);
2880 launcherApps.showAppDetailsForProfile(componentName, user);
2881 } catch (SecurityException e) {
2882 Toast.makeText(this, R.string.activity_not_found, Toast.LENGTH_SHORT).show();
2883 Log.e(TAG, "Launcher does not have permission to launch settings");
2884 } catch (ActivityNotFoundException e) {
2885 Toast.makeText(this, R.string.activity_not_found, Toast.LENGTH_SHORT).show();
2886 Log.e(TAG, "Unable to launch settings");
2887 }
2888 }
2889
2890 // returns true if the activity was started
2891 boolean startApplicationUninstallActivity(ComponentName componentName, int flags,
2892 UserHandleCompat user) {
2893 if ((flags & AppInfo.DOWNLOADED_FLAG) == 0) {
2894 // System applications cannot be installed. For now, show a toast explaining that.
2895 // We may give them the option of disabling apps this way.
2896 int messageId = R.string.uninstall_system_app_text;
2897 Toast.makeText(this, messageId, Toast.LENGTH_SHORT).show();
2898 return false;
2899 } else {
2900 String packageName = componentName.getPackageName();
2901 String className = componentName.getClassName();
2902 Intent intent = new Intent(
2903 Intent.ACTION_DELETE, Uri.fromParts("package", packageName, className));
2904 intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK |
2905 Intent.FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS);
2906 if (user != null) {
2907 user.addToIntent(intent, Intent.EXTRA_USER);
2908 }
2909 startActivity(intent);
2910 return true;
2911 }
2912 }
2913
2914 boolean startActivity(View v, Intent intent, Object tag) {
2915 intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
2916 try {
2917 // Only launch using the new animation if the shortcut has not opted out (this is a
2918 // private contract between launcher and may be ignored in the future).
2919 boolean useLaunchAnimation = (v != null) &&
2920 !intent.hasExtra(INTENT_EXTRA_IGNORE_LAUNCH_ANIMATION);
2921 LauncherAppsCompat launcherApps = LauncherAppsCompat.getInstance(this);
2922 UserManagerCompat userManager = UserManagerCompat.getInstance(this);
2923
2924 UserHandleCompat user = null;
2925 if (intent.hasExtra(AppInfo.EXTRA_PROFILE)) {
2926 long serialNumber = intent.getLongExtra(AppInfo.EXTRA_PROFILE, -1);
2927 user = userManager.getUserForSerialNumber(serialNumber);
2928 }
2929
2930 Bundle optsBundle = null;
2931 <<<<<<< GitAnalyzerPlus_ours
2932 if (useLaunchAnimation) {
2933 ActivityOptions opts = null;
2934 if (sClipRevealMethod != null) {
2935 // TODO: call method directly when Launcher3 can depend on M APIs
2936 int left = 0, top = 0;
2937 int width = v.getMeasuredWidth(), height = v.getMeasuredHeight();
2938 if (v instanceof TextView) {
2939 // Launch from center of icon, not entire view
2940 Drawable icon = Workspace.getTextViewIcon((TextView) v);
2941 if (icon != null) {
2942 Rect bounds = icon.getBounds();
2943 left = (width - bounds.width()) / 2;
2944 top = v.getPaddingTop();
2945 width = bounds.width();
2946 height = bounds.height();
2947 }
2948 }
2949 try {
2950 opts = (ActivityOptions) sClipRevealMethod.invoke(null, v,
2951 left, top, width, height);
2952 } catch (IllegalAccessException e) {
2953 Log.d(TAG, "Could not call makeClipRevealAnimation: " + e);
2954 sClipRevealMethod = null;
2955 } catch (InvocationTargetException e) {
2956 Log.d(TAG, "Could not call makeClipRevealAnimation: " + e);
2957 sClipRevealMethod = null;
2958 }
2959 }
2960 if (opts == null && !Utilities.isLmpOrAbove()) {
2961 opts = ActivityOptions.makeScaleUpAnimation(v, 0, 0,
2962 v.getMeasuredWidth(), v.getMeasuredHeight());
2963 }
2964 optsBundle = opts != null ? opts.toBundle() : null;
2965 ||||||| GitAnalyzerPlus_base
2966 !intent.hasExtra(INTENT_EXTRA_IGNORE_LAUNCH_ANIMATION);
2967 LauncherAppsCompat launcherApps = LauncherAppsCompat.getInstance(this);
2968 UserManagerCompat userManager = UserManagerCompat.getInstance(this);
2969
2970 UserHandleCompat user = null;
2971 if (intent.hasExtra(AppInfo.EXTRA_PROFILE)) {
2972 long serialNumber = intent.getLongExtra(AppInfo.EXTRA_PROFILE, -1);
2973 user = userManager.getUserForSerialNumber(serialNumber);
2974 }
2975
2976 Bundle optsBundle = null;
2977 if (useLaunchAnimation) {
2978 ActivityOptions opts = Utilities.isLmpOrAbove() ?
2979 ActivityOptions.makeCustomAnimation(this, R.anim.task_open_enter, R.anim.no_anim)🔵
2980 ActivityOptions.makeScaleUpAnimation(v, 0, 0, v.getMeasuredWidth(), v.getMeasured🔵
2981 optsBundle = opts.toBundle();
2982 }
2983
2984 if (user == null || user.equals(UserHandleCompat.myUserHandle())) {
2985 // Could be launching some bookkeeping activity
2986 startActivity(intent, optsBundle);
2987 } else {
2988 // TODO Component can be null when shortcuts are supported for secondary user
2989 launcherApps.startActivityForProfile(intent.getComponent(), user,
2990 intent.getSourceBounds(), optsBundle);
2991 }
2992 return true;
2993 } catch (SecurityException e) {
2994 Toast.makeText(this, R.string.activity_not_found, Toast.LENGTH_SHORT).show();
2995 Log.e(TAG, "Launcher does not have the permission to launch " + intent +
2996 ". Make sure to create a MAIN intent-filter for the corresponding activity " +
2997 "or use the exported attribute for this activity. "
2998 + "tag="+ tag + " intent=" + intent, e);
2999 =======
3000 if (useLaunchAnimation && !Utilities.isLmpOrAbove()) {
3001 // On pre-L devices, we use the scale up transition.
3002 // Otherwise we use system default.
3003 ActivityOptions opts =
3004 ActivityOptions.makeScaleUpAnimation(v, 0, 0, v.getMeasuredWidth(), v.getMeasured🔵
3005 optsBundle = opts.toBundle();
3006 >>>>>>> GitAnalyzerPlus_theirs
3007 }
3008
3009 if (user == null || user.equals(UserHandleCompat.myUserHandle())) {
3010 // Could be launching some bookkeeping activity
3011 startActivity(intent, optsBundle);
3012 } else {
3013 // TODO Component can be null when shortcuts are supported for secondary user
3014 launcherApps.startActivityForProfile(intent.getComponent(), user,
3015 intent.getSourceBounds(), optsBundle);
3016 }
3017 return true;
3018 } catch (SecurityException e) {
3019 Toast.makeText(this, R.string.activity_not_found, Toast.LENGTH_SHORT).show();
3020 Log.e(TAG, "Launcher does not have the permission to launch " + intent +
3021 ". Make sure to create a MAIN intent-filter for the corresponding activity " +
3022 "or use the exported attribute for this activity. "
3023 + "tag="+ tag + " intent=" + intent, e);
3024 }
3025 return false;
3026 }
3027
3028 boolean startActivitySafely(View v, Intent intent, Object tag) {
3029 boolean success = false;
3030 if (mIsSafeModeEnabled && !Utilities.isSystemApp(this, intent)) {
3031 Toast.makeText(this, R.string.safemode_shortcut_error, Toast.LENGTH_SHORT).show();
3032 return false;
3033 }
3034 try {
3035 success = startActivity(v, intent, tag);
3036 } catch (ActivityNotFoundException e) {
3037 Toast.makeText(this, R.string.activity_not_found, Toast.LENGTH_SHORT).show();
3038 Log.e(TAG, "Unable to launch. tag=" + tag + " intent=" + intent, e);
3039 }
3040 return success;
3041 }
3042
3043 /**
3044 * This method draws the FolderIcon to an ImageView and then adds and positions that ImageView
3045 * in the DragLayer in the exact absolute location of the original FolderIcon.
3046 */
3047 private void copyFolderIconToImage(FolderIcon fi) {
3048 final int width = fi.getMeasuredWidth();
3049 final int height = fi.getMeasuredHeight();
3050
3051 // Lazy load ImageView, Bitmap and Canvas
3052 if (mFolderIconImageView == null) {
3053 mFolderIconImageView = new ImageView(this);
3054 }
3055 if (mFolderIconBitmap == null || mFolderIconBitmap.getWidth() != width ||
3056 mFolderIconBitmap.getHeight() != height) {
3057 mFolderIconBitmap = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888);
3058 mFolderIconCanvas = new Canvas(mFolderIconBitmap);
3059 }
3060
3061 DragLayer.LayoutParams lp;
3062 if (mFolderIconImageView.getLayoutParams() instanceof DragLayer.LayoutParams) {
3063 lp = (DragLayer.LayoutParams) mFolderIconImageView.getLayoutParams();
3064 } else {
3065 lp = new DragLayer.LayoutParams(width, height);
3066 }
3067
3068 // The layout from which the folder is being opened may be scaled, adjust the starting
3069 // view size by this scale factor.
3070 float scale = mDragLayer.getDescendantRectRelativeToSelf(fi, mRectForFolderAnimation);
3071 lp.customPosition = true;
3072 lp.x = mRectForFolderAnimation.left;
3073 lp.y = mRectForFolderAnimation.top;
3074 lp.width = (int) (scale * width);
3075 lp.height = (int) (scale * height);
3076
3077 mFolderIconCanvas.drawColor(0, PorterDuff.Mode.CLEAR);
3078 fi.draw(mFolderIconCanvas);
3079 mFolderIconImageView.setImageBitmap(mFolderIconBitmap);
3080 if (fi.getFolder() != null) {
3081 mFolderIconImageView.setPivotX(fi.getFolder().getPivotXForIconAnimation());
3082 mFolderIconImageView.setPivotY(fi.getFolder().getPivotYForIconAnimation());
3083 }
3084 // Just in case this image view is still in the drag layer from a previous animation,
3085 // we remove it and re-add it.
3086 if (mDragLayer.indexOfChild(mFolderIconImageView) != -1) {
3087 mDragLayer.removeView(mFolderIconImageView);
3088 }
3089 mDragLayer.addView(mFolderIconImageView, lp);
3090 if (fi.getFolder() != null) {
3091 fi.getFolder().bringToFront();
3092 }
3093 }
3094
3095 private void growAndFadeOutFolderIcon(FolderIcon fi) {
3096 if (fi == null) return;
3097 PropertyValuesHolder alpha = PropertyValuesHolder.ofFloat("alpha", 0);
3098 PropertyValuesHolder scaleX = PropertyValuesHolder.ofFloat("scaleX", 1.5f);
3099 PropertyValuesHolder scaleY = PropertyValuesHolder.ofFloat("scaleY", 1.5f);
3100
3101 FolderInfo info = (FolderInfo) fi.getTag();
3102 if (info.container == LauncherSettings.Favorites.CONTAINER_HOTSEAT) {
3103 CellLayout cl = (CellLayout) fi.getParent().getParent();
3104 CellLayout.LayoutParams lp = (CellLayout.LayoutParams) fi.getLayoutParams();
3105 cl.setFolderLeaveBehindCell(lp.cellX, lp.cellY);
3106 }
3107
3108 // Push an ImageView copy of the FolderIcon into the DragLayer and hide the original
3109 copyFolderIconToImage(fi);
3110 fi.setVisibility(View.INVISIBLE);
3111
3112 ObjectAnimator oa = LauncherAnimUtils.ofPropertyValuesHolder(mFolderIconImageView, alpha,
3113 scaleX, scaleY);
3114 if (Utilities.isLmpOrAbove()) {
3115 oa.setInterpolator(new LogDecelerateInterpolator(100, 0));
3116 }
3117 oa.setDuration(getResources().getInteger(R.integer.config_folderExpandDuration));
3118 oa.start();
3119 }
3120
3121 private void shrinkAndFadeInFolderIcon(final FolderIcon fi) {
3122 if (fi == null) return;
3123 PropertyValuesHolder alpha = PropertyValuesHolder.ofFloat("alpha", 1.0f);
3124 PropertyValuesHolder scaleX = PropertyValuesHolder.ofFloat("scaleX", 1.0f);
3125 PropertyValuesHolder scaleY = PropertyValuesHolder.ofFloat("scaleY", 1.0f);
3126
3127 final CellLayout cl = (CellLayout) fi.getParent().getParent();
3128
3129 // We remove and re-draw the FolderIcon in-case it has changed
3130 mDragLayer.removeView(mFolderIconImageView);
3131 copyFolderIconToImage(fi);
3132 ObjectAnimator oa = LauncherAnimUtils.ofPropertyValuesHolder(mFolderIconImageView, alpha,
3133 scaleX, scaleY);
3134 oa.setDuration(getResources().getInteger(R.integer.config_folderExpandDuration));
3135 oa.addListener(new AnimatorListenerAdapter() {
3136 @Override
3137 public void onAnimationEnd(Animator animation) {
3138 if (cl != null) {
3139 cl.clearFolderLeaveBehind();
3140 // Remove the ImageView copy of the FolderIcon and make the original visible.
3141 mDragLayer.removeView(mFolderIconImageView);
3142 fi.setVisibility(View.VISIBLE);
3143 }
3144 }
3145 });
3146 oa.start();
3147 }
3148
3149 /**
3150 * Opens the user folder described by the specified tag. The opening of the folder
3151 * is animated relative to the specified View. If the View is null, no animation
3152 * is played.
3153 *
3154 * @param folderInfo The FolderInfo describing the folder to open.
3155 */
3156 public void openFolder(FolderIcon folderIcon) {
3157 Folder folder = folderIcon.getFolder();
3158 Folder openFolder = mWorkspace != null ? mWorkspace.getOpenFolder() : null;
3159 if (openFolder != null && openFolder != folder) {
3160 // Close any open folder before opening a folder.
3161 closeFolder();
3162 }
3163
3164 FolderInfo info = folder.mInfo;
3165
3166 info.opened = true;
3167
3168 // While the folder is open, the position of the icon cannot change.
3169 ((CellLayout.LayoutParams) folderIcon.getLayoutParams()).canReorder = false;
3170
3171 // Just verify that the folder hasn't already been added to the DragLayer.
3172 // There was a one-off crash where the folder had a parent already.
3173 if (folder.getParent() == null) {
3174 mDragLayer.addView(folder);
3175 mDragController.addDropTarget((DropTarget) folder);
3176 } else {
3177 Log.w(TAG, "Opening folder (" + folder + ") which already has a parent (" +
3178 folder.getParent() + ").");
3179 }
3180 folder.animateOpen();
3181 growAndFadeOutFolderIcon(folderIcon);
3182
3183 // Notify the accessibility manager that this folder "window" has appeared and occluded
3184 // the workspace items
3185 folder.sendAccessibilityEvent(AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED);
3186 getDragLayer().sendAccessibilityEvent(AccessibilityEvent.TYPE_WINDOW_CONTENT_CHANGED);
3187 }
3188
3189 public void closeFolder() {
3190 Folder folder = mWorkspace != null ? mWorkspace.getOpenFolder() : null;
3191 if (folder != null) {
3192 if (folder.isEditingName()) {
3193 folder.dismissEditingName();
3194 }
3195 closeFolder(folder);
3196 }
3197 }
3198
3199 void closeFolder(Folder folder) {
3200 folder.getInfo().opened = false;
3201
3202 ViewGroup parent = (ViewGroup) folder.getParent().getParent();
3203 if (parent != null) {
3204 FolderIcon fi = (FolderIcon) mWorkspace.getViewForTag(folder.mInfo);
3205 shrinkAndFadeInFolderIcon(fi);
3206 if (fi != null) {
3207 ((CellLayout.LayoutParams) fi.getLayoutParams()).canReorder = true;
3208 }
3209 }
3210 folder.animateClosed();
3211
3212 // Notify the accessibility manager that this folder "window" has disappeard and no
3213 // longer occludeds the workspace items
3214 getDragLayer().sendAccessibilityEvent(AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED);
3215 }
3216
3217 public boolean onLongClick(View v) {
3218 if (!isDraggingEnabled()) return false;
3219 if (isWorkspaceLocked()) return false;
3220 if (mState != State.WORKSPACE) return false;
3221
3222 if (v instanceof Workspace) {
3223 if (!mWorkspace.isInOverviewMode()) {
3224 if (mWorkspace.enterOverviewMode()) {
3225 mWorkspace.performHapticFeedback(HapticFeedbackConstants.LONG_PRESS,
3226 HapticFeedbackConstants.FLAG_IGNORE_VIEW_SETTING);
3227 return true;
3228 } else {
3229 return false;
3230 }
3231 } else {
3232 return false;
3233 }
3234 }
3235
3236 CellLayout.CellInfo longClickCellInfo = null;
3237 View itemUnderLongClick = null;
3238 if (v.getTag() instanceof ItemInfo) {
3239 ItemInfo info = (ItemInfo) v.getTag();
3240 longClickCellInfo = new CellLayout.CellInfo(v, info);
3241 itemUnderLongClick = longClickCellInfo.cell;
3242 resetAddInfo();
3243 }
3244
3245 // The hotseat touch handling does not go through Workspace, and we always allow long press
3246 // on hotseat items.
3247 final boolean inHotseat = isHotseatLayout(v);
3248 boolean allowLongPress = inHotseat || mWorkspace.allowLongPress();
3249 if (allowLongPress && !mDragController.isDragging()) {
3250 if (itemUnderLongClick == null) {
3251 // User long pressed on empty space
3252 mWorkspace.performHapticFeedback(HapticFeedbackConstants.LONG_PRESS,
3253 HapticFeedbackConstants.FLAG_IGNORE_VIEW_SETTING);
3254 if (mWorkspace.isInOverviewMode()) {
3255 mWorkspace.startReordering(v);
3256 } else {
3257 mWorkspace.enterOverviewMode();
3258 }
3259 } else {
3260 final boolean isAllAppsButton = inHotseat && isAllAppsButtonRank(
3261 mHotseat.getOrderInHotseat(
3262 longClickCellInfo.cellX,
3263 longClickCellInfo.cellY));
3264 if (!(itemUnderLongClick instanceof Folder || isAllAppsButton)) {
3265 // User long pressed on an item
3266 mWorkspace.startDrag(longClickCellInfo);
3267 }
3268 }
3269 }
3270 return true;
3271 }
3272
3273 boolean isHotseatLayout(View layout) {
3274 return mHotseat != null && layout != null &&
3275 (layout instanceof CellLayout) && (layout == mHotseat.getLayout());
3276 }
3277
3278 /**
3279 * Returns the CellLayout of the specified container at the specified screen.
3280 */
3281 public CellLayout getCellLayout(long container, long screenId) {
3282 if (container == LauncherSettings.Favorites.CONTAINER_HOTSEAT) {
3283 if (mHotseat != null) {
3284 return mHotseat.getLayout();
3285 } else {
3286 return null;
3287 }
3288 } else {
3289 return mWorkspace.getScreenWithId(screenId);
3290 }
3291 }
3292
3293 /**
3294 * For overridden classes.
3295 */
3296 public boolean isAllAppsVisible() {
3297 return isAppsViewVisible();
3298 }
3299
3300 public boolean isAppsViewVisible() {
3301 return (mState == State.APPS) || (mOnResumeState == State.APPS);
3302 }
3303
3304 public boolean isWidgetsViewVisible() {
3305 return (mState == State.WIDGETS) || (mOnResumeState == State.WIDGETS);
3306 }
3307
3308 private void setWorkspaceBackground(boolean workspace) {
3309 mLauncherView.setBackground(workspace ?
3310 mWorkspaceBackgroundDrawable : null);
3311 }
3312
3313 protected void changeWallpaperVisiblity(boolean visible) {
3314 int wpflags = visible ? WindowManager.LayoutParams.FLAG_SHOW_WALLPAPER : 0;
3315 int curflags = getWindow().getAttributes().flags
3316 & WindowManager.LayoutParams.FLAG_SHOW_WALLPAPER;
3317 if (wpflags != curflags) {
3318 getWindow().setFlags(wpflags, WindowManager.LayoutParams.FLAG_SHOW_WALLPAPER);
3319 }
3320 setWorkspaceBackground(visible);
3321 }
3322
3323 @Override
3324 public void onTrimMemory(int level) {
3325 super.onTrimMemory(level);
3326 if (level >= ComponentCallbacks2.TRIM_MEMORY_UI_HIDDEN) {
3327 // The widget preview db can result in holding onto over
3328 // 3MB of memory for caching which isn't necessary.
3329 SQLiteDatabase.releaseMemory();
3330
3331 // This clears all widget bitmaps from the widget tray
3332 // TODO(hyunyoungs)
3333 }
3334 if (mLauncherCallbacks != null) {
3335 mLauncherCallbacks.onTrimMemory(level);
3336 }
3337 }
3338
3339 @Override
3340 public void onStateTransitionHideSearchBar() {
3341 // Hide the search bar
3342 if (mSearchDropTargetBar != null) {
3343 mSearchDropTargetBar.hideSearchBar(false /* animated */);
3344 }
3345 }
3346
3347 protected void showWorkspace(boolean animated) {
3348 showWorkspace(animated, null);
3349 }
3350
3351 void showWorkspace(boolean animated, Runnable onCompleteRunnable) {
3352 boolean changed = mState != State.WORKSPACE ||
3353 mWorkspace.getState() != Workspace.State.NORMAL;
3354 if (changed) {
3355 boolean wasInSpringLoadedMode = (mState != State.WORKSPACE);
3356 mWorkspace.setVisibility(View.VISIBLE);
3357 mStateTransitionAnimation.startAnimationToWorkspace(mState, Workspace.State.NORMAL,
3358 animated, onCompleteRunnable);
3359
3360 // Show the search bar (only animate if we were showing the drop target bar in spring
3361 // loaded mode)
3362 if (mSearchDropTargetBar != null) {
3363 mSearchDropTargetBar.showSearchBar(animated && wasInSpringLoadedMode);
3364 }
3365
3366 // Set focus to the AppsCustomize button
3367 if (mAllAppsButton != null) {
3368 mAllAppsButton.requestFocus();
3369 }
3370 }
3371
3372 // Change the state *after* we've called all the transition code
3373 mState = State.WORKSPACE;
3374
3375 // Resume the auto-advance of widgets
3376 mUserPresent = true;
3377 updateAutoAdvanceState();
3378
3379 if (changed) {
3380 // Send an accessibility event to announce the context change
3381 getWindow().getDecorView()
3382 .sendAccessibilityEvent(AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED);
3383
3384 onWorkspaceShown(animated);
3385 }
3386 }
3387
3388 void showOverviewMode(boolean animated) {
3389 mWorkspace.setVisibility(View.VISIBLE);
3390 mStateTransitionAnimation.startAnimationToWorkspace(mState, Workspace.State.OVERVIEW,
3391 animated, null /* onCompleteRunnable */);
3392 mState = State.WORKSPACE;
3393 onWorkspaceShown(animated);
3394 }
3395
3396 public void onWorkspaceShown(boolean animated) {
3397 }
3398
3399 /**
3400 * Shows the apps view.
3401 */
3402 void showAppsView(boolean animated, boolean resetListToTop) {
3403 if (resetListToTop) {
3404 mAppsView.scrollToTop();
3405 }
3406 showAppsOrWidgets(animated, State.APPS);
3407 }
3408
3409 /**
3410 * Shows the widgets view.
3411 */
3412 void showWidgetsView(boolean animated, boolean resetPageToZero) {
3413 Log.d(TAG, "showWidgetsView:" + animated + " resetPageToZero:" + resetPageToZero);
3414 if (resetPageToZero) {
3415 mWidgetsView.scrollToTop();
3416 }
3417 showAppsOrWidgets(animated, State.WIDGETS);
3418
3419 mWidgetsView.post(new Runnable() {
3420 @Override
3421 public void run() {
3422 mWidgetsView.requestFocus();
3423 }
3424 });
3425 }
3426
3427 /**
3428 * Sets up the transition to show the apps/widgets view.
3429 */
3430 private void showAppsOrWidgets(boolean animated, State toState) {
3431 if (mState != State.WORKSPACE) return;
3432 if (toState != State.APPS && toState != State.WIDGETS) return;
3433
3434 if (toState == State.APPS) {
3435 mStateTransitionAnimation.startAnimationToAllApps(animated);
3436 if (mLauncherCallbacks != null) {
3437 mLauncherCallbacks.onAllAppsShown();
3438 }
3439 } else {
3440 mStateTransitionAnimation.startAnimationToWidgets(animated);
3441 }
3442
3443 // Change the state *after* we've called all the transition code
3444 mState = toState;
3445
3446 // Pause the auto-advance of widgets until we are out of AllApps
3447 mUserPresent = false;
3448 updateAutoAdvanceState();
3449 closeFolder();
3450
3451 // Send an accessibility event to announce the context change
3452 getWindow().getDecorView()
3453 .sendAccessibilityEvent(AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED);
3454 }
3455
3456 public void enterSpringLoadedDragMode() {
3457 Log.d(TAG, String.format("enterSpringLoadedDragMode [mState=%s",
3458 mState.name()));
3459 if (mState == State.WORKSPACE || mState == State.APPS_SPRING_LOADED ||
3460 mState == State.WIDGETS_SPRING_LOADED) {
3461 return;
3462 }
3463
3464 mStateTransitionAnimation.startAnimationToWorkspace(mState, Workspace.State.SPRING_LOADED,
3465 true /* animated */, null /* onCompleteRunnable */);
3466 mState = isAppsViewVisible() ? State.APPS_SPRING_LOADED : State.WIDGETS_SPRING_LOADED;
3467 }
3468
3469 public void exitSpringLoadedDragModeDelayed(final boolean successfulDrop, int delay,
3470 final Runnable onCompleteRunnable) {
3471 if (mState != State.APPS_SPRING_LOADED && mState != State.WIDGETS_SPRING_LOADED) return;
3472
3473 mHandler.postDelayed(new Runnable() {
3474 @Override
3475 public void run() {
3476 if (successfulDrop) {
3477 // TODO(hyunyoungs): verify if this hack is still needed, if not, delete.
3478 //
3479 // Before we show workspace, hide all apps again because
3480 // exitSpringLoadedDragMode made it visible. This is a bit hacky; we should
3481 // clean up our state transition functions
3482 mWidgetsView.setVisibility(View.GONE);
3483 showWorkspace(true, onCompleteRunnable);
3484 } else {
3485 exitSpringLoadedDragMode();
3486 }
3487 }
3488 }, delay);
3489 }
3490
3491 void exitSpringLoadedDragMode() {
3492 if (mState == State.APPS_SPRING_LOADED) {
3493 mStateTransitionAnimation.startAnimationToAllApps(true /* animated */);
3494 mState = State.APPS;
3495 } else if (mState == State.WIDGETS_SPRING_LOADED) {
3496 mStateTransitionAnimation.startAnimationToWidgets(true /* animated */);
3497 mState = State.WIDGETS;
3498 }
3499 // Otherwise, we are not in spring loaded mode, so don't do anything.
3500 }
3501
3502 void lockAllApps() {
3503 // TODO
3504 }
3505
3506 void unlockAllApps() {
3507 // TODO
3508 }
3509
3510 protected void disableVoiceButtonProxy(boolean disable) {
3511 // NO-OP
3512 }
3513
3514 public View getOrCreateQsbBar() {
3515 if (mLauncherCallbacks != null && mLauncherCallbacks.providesSearch()) {
3516 return mLauncherCallbacks.getQsbBar();
3517 }
3518
3519 if (mQsb == null) {
3520 AppWidgetProviderInfo searchProvider = Utilities.getSearchWidgetProvider(this);
3521 if (searchProvider == null) {
3522 return null;
3523 }
3524
3525 Bundle opts = new Bundle();
3526 opts.putInt(AppWidgetManager.OPTION_APPWIDGET_HOST_CATEGORY,
3527 AppWidgetProviderInfo.WIDGET_CATEGORY_SEARCHBOX);
3528
3529 SharedPreferences sp = getSharedPreferences(
3530 LauncherAppState.getSharedPreferencesKey(), MODE_PRIVATE);
3531 int widgetId = sp.getInt(QSB_WIDGET_ID, -1);
3532 AppWidgetProviderInfo widgetInfo = mAppWidgetManager.getAppWidgetInfo(widgetId);
3533 if (!searchProvider.provider.flattenToString().equals(
3534 sp.getString(QSB_WIDGET_PROVIDER, null))
3535 || (widgetInfo == null)
3536 || !widgetInfo.provider.equals(searchProvider.provider)) {
3537 // A valid widget is not already bound.
3538 if (widgetId > -1) {
3539 mAppWidgetHost.deleteAppWidgetId(widgetId);
3540 widgetId = -1;
3541 }
3542
3543 // Try to bind a new widget
3544 widgetId = mAppWidgetHost.allocateAppWidgetId();
3545
3546 if (!AppWidgetManagerCompat.getInstance(this)
3547 .bindAppWidgetIdIfAllowed(widgetId, searchProvider, opts)) {
3548 mAppWidgetHost.deleteAppWidgetId(widgetId);
3549 widgetId = -1;
3550 }
3551
3552 sp.edit()
3553 .putInt(QSB_WIDGET_ID, widgetId)
3554 .putString(QSB_WIDGET_PROVIDER, searchProvider.provider.flattenToString())
3555 .commit();
3556 }
3557
3558 if (widgetId != -1) {
3559 mQsb = mAppWidgetHost.createView(this, widgetId, searchProvider);
3560 mQsb.updateAppWidgetOptions(opts);
3561 mQsb.setPadding(0, 0, 0, 0);
3562 mSearchDropTargetBar.addView(mQsb);
3563 mSearchDropTargetBar.setQsbSearchBar(mQsb);
3564 }
3565 }
3566 return mQsb;
3567 }
3568
3569 private void reinflateQSBIfNecessary() {
3570 if (mQsb instanceof LauncherAppWidgetHostView &&
3571 ((LauncherAppWidgetHostView) mQsb).isReinflateRequired()) {
3572 mSearchDropTargetBar.removeView(mQsb);
3573 mQsb = null;
3574 mSearchDropTargetBar.setQsbSearchBar(getOrCreateQsbBar());
3575 }
3576 }
3577
3578 @Override
3579 public boolean dispatchPopulateAccessibilityEvent(AccessibilityEvent event) {
3580 final boolean result = super.dispatchPopulateAccessibilityEvent(event);
3581 final List<CharSequence> text = event.getText();
3582 text.clear();
3583 // Populate event with a fake title based on the current state.
3584 if (mState == State.APPS) {
3585 text.add("Apps");
3586 } else if (mState == State.WIDGETS) {
3587 text.add("Widgets");
3588 } else {
3589 text.add(getString(R.string.all_apps_home_button_label));
3590 }
3591 return result;
3592 }
3593
3594 /**
3595 * Receives notifications when system dialogs are to be closed.
3596 */
3597 @Thunk class CloseSystemDialogsIntentReceiver extends BroadcastReceiver {
3598 @Override
3599 public void onReceive(Context context, Intent intent) {
3600 closeSystemDialogs();
3601 }
3602 }
3603
3604 /**
3605 * Receives notifications whenever the appwidgets are reset.
3606 */
3607 private class AppWidgetResetObserver extends ContentObserver {
3608 public AppWidgetResetObserver() {
3609 super(new Handler());
3610 }
3611
3612 @Override
3613 public void onChange(boolean selfChange) {
3614 onAppWidgetReset();
3615 }
3616 }
3617
3618 /**
3619 * If the activity is currently paused, signal that we need to run the passed Runnable
3620 * in onResume.
3621 *
3622 * This needs to be called from incoming places where resources might have been loaded
3623 * while we are paused. That is becaues the Configuration might be wrong
3624 * when we're not running, and if it comes back to what it was when we
3625 * were paused, we are not restarted.
3626 *
3627 * Implementation of the method from LauncherModel.Callbacks.
3628 *
3629 * @return true if we are currently paused. The caller might be able to
3630 * skip some work in that case since we will come back again.
3631 */
3632 private boolean waitUntilResume(Runnable run, boolean deletePreviousRunnables) {
3633 if (mPaused) {
3634 Log.i(TAG, "Deferring update until onResume");
3635 if (deletePreviousRunnables) {
3636 while (mBindOnResumeCallbacks.remove(run)) {
3637 }
3638 }
3639 mBindOnResumeCallbacks.add(run);
3640 return true;
3641 } else {
3642 return false;
3643 }
3644 }
3645
3646 private boolean waitUntilResume(Runnable run) {
3647 return waitUntilResume(run, false);
3648 }
3649
3650 public void addOnResumeCallback(Runnable run) {
3651 mOnResumeCallbacks.add(run);
3652 }
3653
3654 /**
3655 * If the activity is currently paused, signal that we need to re-run the loader
3656 * in onResume.
3657 *
3658 * This needs to be called from incoming places where resources might have been loaded
3659 * while we are paused. That is becaues the Configuration might be wrong
3660 * when we're not running, and if it comes back to what it was when we
3661 * were paused, we are not restarted.
3662 *
3663 * Implementation of the method from LauncherModel.Callbacks.
3664 *
3665 * @return true if we are currently paused. The caller might be able to
3666 * skip some work in that case since we will come back again.
3667 */
3668 public boolean setLoadOnResume() {
3669 if (mPaused) {
3670 Log.i(TAG, "setLoadOnResume");
3671 mOnResumeNeedsLoad = true;
3672 return true;
3673 } else {
3674 return false;
3675 }
3676 }
3677
3678 /**
3679 * Implementation of the method from LauncherModel.Callbacks.
3680 */
3681 public int getCurrentWorkspaceScreen() {
3682 if (mWorkspace != null) {
3683 return mWorkspace.getCurrentPage();
3684 } else {
3685 return SCREEN_COUNT / 2;
3686 }
3687 }
3688
3689 /**
3690 * Refreshes the shortcuts shown on the workspace.
3691 *
3692 * Implementation of the method from LauncherModel.Callbacks.
3693 */
3694 public void startBinding() {
3695 setWorkspaceLoading(true);
3696
3697 // If we're starting binding all over again, clear any bind calls we'd postponed in
3698 // the past (see waitUntilResume) -- we don't need them since we're starting binding
3699 // from scratch again
3700 mBindOnResumeCallbacks.clear();
3701
3702 // Clear the workspace because it's going to be rebound
3703 mWorkspace.clearDropTargets();
3704 mWorkspace.removeAllWorkspaceScreens();
3705
3706 mWidgetsToAdvance.clear();
3707 if (mHotseat != null) {
3708 mHotseat.resetLayout();
3709 }
3710 }
3711
3712 @Override
3713 public void bindScreens(ArrayList<Long> orderedScreenIds) {
3714 bindAddScreens(orderedScreenIds);
3715
3716 // If there are no screens, we need to have an empty screen
3717 if (orderedScreenIds.size() == 0) {
3718 mWorkspace.addExtraEmptyScreen();
3719 }
3720
3721 // Create the custom content page (this call updates mDefaultScreen which calls
3722 // setCurrentPage() so ensure that all pages are added before calling this).
3723 if (hasCustomContentToLeft()) {
3724 mWorkspace.createCustomContentContainer();
3725 populateCustomContentContainer();
3726 }
3727 }
3728
3729 @Override
3730 public void bindAddScreens(ArrayList<Long> orderedScreenIds) {
3731 // Log to disk
3732 Launcher.addDumpLog(TAG, "11683562 - bindAddScreens()", true);
3733 Launcher.addDumpLog(TAG, "11683562 - orderedScreenIds: " +
3734 TextUtils.join(", ", orderedScreenIds), true);
3735 int count = orderedScreenIds.size();
3736 for (int i = 0; i < count; i++) {
3737 mWorkspace.insertNewWorkspaceScreenBeforeEmptyScreen(orderedScreenIds.get(i));
3738 }
3739 }
3740
3741 @Override
3742 public void bindAddPendingItem(final PendingAddItemInfo info, final long container,
3743 final long screenId, final int[] cell, final int spanX, final int spanY) {
3744 showWorkspace(true, new Runnable() {
3745
3746 @Override
3747 public void run() {
3748 mWorkspace.snapToPage(mWorkspace.getPageIndexForScreenId(screenId));
3749 addPendingItem(info, container, screenId, cell, spanX, spanY);
3750 }
3751 });
3752 }
3753
3754 private boolean shouldShowWeightWatcher() {
3755 String spKey = LauncherAppState.getSharedPreferencesKey();
3756 SharedPreferences sp = getSharedPreferences(spKey, Context.MODE_PRIVATE);
3757 boolean show = sp.getBoolean(SHOW_WEIGHT_WATCHER, SHOW_WEIGHT_WATCHER_DEFAULT);
3758
3759 return show;
3760 }
3761
3762 private void toggleShowWeightWatcher() {
3763 String spKey = LauncherAppState.getSharedPreferencesKey();
3764 SharedPreferences sp = getSharedPreferences(spKey, Context.MODE_PRIVATE);
3765 boolean show = sp.getBoolean(SHOW_WEIGHT_WATCHER, true);
3766
3767 show = !show;
3768
3769 SharedPreferences.Editor editor = sp.edit();
3770 editor.putBoolean(SHOW_WEIGHT_WATCHER, show);
3771 editor.commit();
3772
3773 if (mWeightWatcher != null) {
3774 mWeightWatcher.setVisibility(show ? View.VISIBLE : View.GONE);
3775 }
3776 }
3777
3778 public void bindAppsAdded(final ArrayList<Long> newScreens,
3779 final ArrayList<ItemInfo> addNotAnimated,
3780 final ArrayList<ItemInfo> addAnimated,
3781 final ArrayList<AppInfo> addedApps) {
3782 Runnable r = new Runnable() {
3783 public void run() {
3784 bindAppsAdded(newScreens, addNotAnimated, addAnimated, addedApps);
3785 }
3786 };
3787 if (waitUntilResume(r)) {
3788 return;
3789 }
3790
3791 // Add the new screens
3792 if (newScreens != null) {
3793 bindAddScreens(newScreens);
3794 }
3795
3796 // We add the items without animation on non-visible pages, and with
3797 // animations on the new page (which we will try and snap to).
3798 if (addNotAnimated != null && !addNotAnimated.isEmpty()) {
3799 bindItems(addNotAnimated, 0,
3800 addNotAnimated.size(), false);
3801 }
3802 if (addAnimated != null && !addAnimated.isEmpty()) {
3803 bindItems(addAnimated, 0,
3804 addAnimated.size(), true);
3805 }
3806
3807 // Remove the extra empty screen
3808 mWorkspace.removeExtraEmptyScreen(false, false);
3809
3810 if (addedApps != null && mAppsView != null) {
3811 mAppsView.addApps(addedApps);
3812 }
3813 }
3814
3815 /**
3816 * Bind the items start-end from the list.
3817 *
3818 * Implementation of the method from LauncherModel.Callbacks.
3819 */
3820 public void bindItems(final ArrayList<ItemInfo> shortcuts, final int start, final int end,
3821 final boolean forceAnimateIcons) {
3822 Runnable r = new Runnable() {
3823 public void run() {
3824 bindItems(shortcuts, start, end, forceAnimateIcons);
3825 }
3826 };
3827 if (waitUntilResume(r)) {
3828 return;
3829 }
3830
3831 // Get the list of added shortcuts and intersect them with the set of shortcuts here
3832 final AnimatorSet anim = LauncherAnimUtils.createAnimatorSet();
3833 final Collection<Animator> bounceAnims = new ArrayList<Animator>();
3834 final boolean animateIcons = forceAnimateIcons && canRunNewAppsAnimation();
3835 Workspace workspace = mWorkspace;
3836 long newShortcutsScreenId = -1;
3837 for (int i = start; i < end; i++) {
3838 final ItemInfo item = shortcuts.get(i);
3839
3840 // Short circuit if we are loading dock items for a configuration which has no dock
3841 if (item.container == LauncherSettings.Favorites.CONTAINER_HOTSEAT &&
3842 mHotseat == null) {
3843 continue;
3844 }
3845
3846 switch (item.itemType) {
3847 case LauncherSettings.Favorites.ITEM_TYPE_APPLICATION:
3848 case LauncherSettings.Favorites.ITEM_TYPE_SHORTCUT:
3849 ShortcutInfo info = (ShortcutInfo) item;
3850 View shortcut = createShortcut(info);
3851
3852 /*
3853 * TODO: FIX collision case
3854 */
3855 if (item.container == LauncherSettings.Favorites.CONTAINER_DESKTOP) {
3856 CellLayout cl = mWorkspace.getScreenWithId(item.screenId);
3857 if (cl != null && cl.isOccupied(item.cellX, item.cellY)) {
3858 View v = cl.getChildAt(item.cellX, item.cellY);
3859 Object tag = v.getTag();
3860 String desc = "Collision while binding workspace item: " + item
3861 + ". Collides with " + tag;
3862 if (LauncherAppState.isDogfoodBuild()) {
3863 throw (new RuntimeException(desc));
3864 } else {
3865 Log.d(TAG, desc);
3866 }
3867 }
3868 }
3869
3870 workspace.addInScreenFromBind(shortcut, item.container, item.screenId, item.cellX,
3871 item.cellY, 1, 1);
3872 if (animateIcons) {
3873 // Animate all the applications up now
3874 shortcut.setAlpha(0f);
3875 shortcut.setScaleX(0f);
3876 shortcut.setScaleY(0f);
3877 bounceAnims.add(createNewAppBounceAnimation(shortcut, i));
3878 newShortcutsScreenId = item.screenId;
3879 }
3880 break;
3881 case LauncherSettings.Favorites.ITEM_TYPE_FOLDER:
3882 FolderIcon newFolder = FolderIcon.fromXml(R.layout.folder_icon, this,
3883 (ViewGroup) workspace.getChildAt(workspace.getCurrentPage()),
3884 (FolderInfo) item, mIconCache);
3885 workspace.addInScreenFromBind(newFolder, item.container, item.screenId, item.cellX,
3886 item.cellY, 1, 1);
3887 break;
3888 default:
3889 throw new RuntimeException("Invalid Item Type");
3890 }
3891 }
3892
3893 if (animateIcons) {
3894 // Animate to the correct page
3895 if (newShortcutsScreenId > -1) {
3896 long currentScreenId = mWorkspace.getScreenIdForPageIndex(mWorkspace.getNextPage());
3897 final int newScreenIndex = mWorkspace.getPageIndexForScreenId(newShortcutsScreenId);
3898 final Runnable startBounceAnimRunnable = new Runnable() {
3899 public void run() {
3900 anim.playTogether(bounceAnims);
3901 anim.start();
3902 }
3903 };
3904 if (newShortcutsScreenId != currentScreenId) {
3905 // We post the animation slightly delayed to prevent slowdowns
3906 // when we are loading right after we return to launcher.
3907 mWorkspace.postDelayed(new Runnable() {
3908 public void run() {
3909 if (mWorkspace != null) {
3910 mWorkspace.snapToPage(newScreenIndex);
3911 mWorkspace.postDelayed(startBounceAnimRunnable,
3912 NEW_APPS_ANIMATION_DELAY);
3913 }
3914 }
3915 }, NEW_APPS_PAGE_MOVE_DELAY);
3916 } else {
3917 mWorkspace.postDelayed(startBounceAnimRunnable, NEW_APPS_ANIMATION_DELAY);
3918 }
3919 }
3920 }
3921 workspace.requestLayout();
3922 }
3923
3924 /**
3925 * Implementation of the method from LauncherModel.Callbacks.
3926 */
3927 public void bindFolders(final HashMap<Long, FolderInfo> folders) {
3928 Runnable r = new Runnable() {
3929 public void run() {
3930 bindFolders(folders);
3931 }
3932 };
3933 if (waitUntilResume(r)) {
3934 return;
3935 }
3936 sFolders.clear();
3937 sFolders.putAll(folders);
3938 }
3939
3940 /**
3941 * Add the views for a widget to the workspace.
3942 *
3943 * Implementation of the method from LauncherModel.Callbacks.
3944 */
3945 public void bindAppWidget(final LauncherAppWidgetInfo item) {
3946 Runnable r = new Runnable() {
3947 public void run() {
3948 bindAppWidget(item);
3949 }
3950 };
3951 if (waitUntilResume(r)) {
3952 return;
3953 }
3954
3955 final long start = DEBUG_WIDGETS ? SystemClock.uptimeMillis() : 0;
3956 if (DEBUG_WIDGETS) {
3957 Log.d(TAG, "bindAppWidget: " + item);
3958 }
3959 final Workspace workspace = mWorkspace;
3960
3961 LauncherAppWidgetProviderInfo appWidgetInfo =
3962 LauncherModel.getProviderInfo(this, item.providerName, item.user);
3963
3964 if (!mIsSafeModeEnabled
3965 && ((item.restoreStatus & LauncherAppWidgetInfo.FLAG_PROVIDER_NOT_READY) == 0)
3966 && ((item.restoreStatus & LauncherAppWidgetInfo.FLAG_ID_NOT_VALID) != 0)) {
3967 if (appWidgetInfo == null) {
3968 if (DEBUG_WIDGETS) {
3969 Log.d(TAG, "Removing restored widget: id=" + item.appWidgetId
3970 + " belongs to component " + item.providerName
3971 + ", as the povider is null");
3972 }
3973 LauncherModel.deleteItemFromDatabase(this, item);
3974 return;
3975 }
3976 // Note: This assumes that the id remap broadcast is received before this step.
3977 // If that is not the case, the id remap will be ignored and user may see the
3978 // click to setup view.
3979 PendingAddWidgetInfo pendingInfo = new PendingAddWidgetInfo(appWidgetInfo, null);
3980 pendingInfo.spanX = item.spanX;
3981 pendingInfo.spanY = item.spanY;
3982 pendingInfo.minSpanX = item.minSpanX;
3983 pendingInfo.minSpanY = item.minSpanY;
3984 Bundle options = null;
3985 // AppsCustomizePagedView.getDefaultOptionsForWidget(this, pendingInfo);
3986
3987 int newWidgetId = mAppWidgetHost.allocateAppWidgetId();
3988 boolean success = mAppWidgetManager.bindAppWidgetIdIfAllowed(
3989 newWidgetId, appWidgetInfo, options);
3990
3991 // TODO consider showing a permission dialog when the widget is clicked.
3992 if (!success) {
3993 mAppWidgetHost.deleteAppWidgetId(newWidgetId);
3994 if (DEBUG_WIDGETS) {
3995 Log.d(TAG, "Removing restored widget: id=" + item.appWidgetId
3996 + " belongs to component " + item.providerName
3997 + ", as the launcher is unable to bing a new widget id");
3998 }
3999 LauncherModel.deleteItemFromDatabase(this, item);
4000 return;
4001 }
4002
4003 item.appWidgetId = newWidgetId;
4004
4005 // If the widget has a configure activity, it is still needs to set it up, otherwise
4006 // the widget is ready to go.
4007 item.restoreStatus = (appWidgetInfo.configure == null)
4008 ? LauncherAppWidgetInfo.RESTORE_COMPLETED
4009 : LauncherAppWidgetInfo.FLAG_UI_NOT_READY;
4010
4011 LauncherModel.updateItemInDatabase(this, item);
4012 }
4013
4014 if (!mIsSafeModeEnabled && item.restoreStatus == LauncherAppWidgetInfo.RESTORE_COMPLETED) {
4015 final int appWidgetId = item.appWidgetId;
4016 if (DEBUG_WIDGETS) {
4017 Log.d(TAG, "bindAppWidget: id=" + item.appWidgetId + " belongs to component "
4018 + appWidgetInfo.provider);
4019 }
4020
4021 item.hostView = mAppWidgetHost.createView(this, appWidgetId, appWidgetInfo);
4022 } else {
4023 appWidgetInfo = null;
4024 PendingAppWidgetHostView view = new PendingAppWidgetHostView(this, item,
4025 mIsSafeModeEnabled);
4026 view.updateIcon(mIconCache);
4027 item.hostView = view;
4028 item.hostView.updateAppWidget(null);
4029 item.hostView.setOnClickListener(this);
4030 }
4031
4032 item.hostView.setTag(item);
4033 item.onBindAppWidget(this);
4034
4035 workspace.addInScreen(item.hostView, item.container, item.screenId, item.cellX,
4036 item.cellY, item.spanX, item.spanY, false);
4037 if (!item.isCustomWidget()) {
4038 addWidgetToAutoAdvanceIfNeeded(item.hostView, appWidgetInfo);
4039 }
4040
4041 workspace.requestLayout();
4042
4043 if (DEBUG_WIDGETS) {
4044 Log.d(TAG, "bound widget id="+item.appWidgetId+" in "
4045 + (SystemClock.uptimeMillis()-start) + "ms");
4046 }
4047 }
4048
4049 /**
4050 * Restores a pending widget.
4051 *
4052 * @param appWidgetId The app widget id
4053 * @param cellInfo The position on screen where to create the widget.
4054 */
4055 private void completeRestoreAppWidget(final int appWidgetId) {
4056 LauncherAppWidgetHostView view = mWorkspace.getWidgetForAppWidgetId(appWidgetId);
4057 if ((view == null) || !(view instanceof PendingAppWidgetHostView)) {
4058 Log.e(TAG, "Widget update called, when the widget no longer exists.");
4059 return;
4060 }
4061
4062 LauncherAppWidgetInfo info = (LauncherAppWidgetInfo) view.getTag();
4063 info.restoreStatus = LauncherAppWidgetInfo.RESTORE_COMPLETED;
4064
4065 mWorkspace.reinflateWidgetsIfNecessary();
4066 LauncherModel.updateItemInDatabase(this, info);
4067 }
4068
4069 public void onPageBoundSynchronously(int page) {
4070 mSynchronouslyBoundPages.add(page);
4071 }
4072
4073 /**
4074 * Callback saying that there aren't any more items to bind.
4075 *
4076 * Implementation of the method from LauncherModel.Callbacks.
4077 */
4078 public void finishBindingItems() {
4079 Runnable r = new Runnable() {
4080 public void run() {
4081 finishBindingItems();
4082 }
4083 };
4084 if (waitUntilResume(r)) {
4085 return;
4086 }
4087 if (mSavedState != null) {
4088 if (!mWorkspace.hasFocus()) {
4089 mWorkspace.getChildAt(mWorkspace.getCurrentPage()).requestFocus();
4090 }
4091 mSavedState = null;
4092 }
4093
4094 mWorkspace.restoreInstanceStateForRemainingPages();
4095
4096 setWorkspaceLoading(false);
4097 sendLoadingCompleteBroadcastIfNecessary();
4098
4099 // If we received the result of any pending adds while the loader was running (e.g. the
4100 // widget configuration forced an orientation change), process them now.
4101 if (sPendingAddItem != null) {
4102 final long screenId = completeAdd(sPendingAddItem);
4103
4104 // TODO: this moves the user to the page where the pending item was added. Ideally,
4105 // the screen would be guaranteed to exist after bind, and the page would be set through
4106 // the workspace restore process.
4107 mWorkspace.post(new Runnable() {
4108 @Override
4109 public void run() {
4110 mWorkspace.snapToScreenId(screenId);
4111 }
4112 });
4113 sPendingAddItem = null;
4114 }
4115
4116 PackageInstallerCompat.getInstance(this).onFinishBind();
4117
4118 if (mLauncherCallbacks != null) {
4119 mLauncherCallbacks.finishBindingItems(false);
4120 }
4121 }
4122
4123 private void sendLoadingCompleteBroadcastIfNecessary() {
4124 if (!mSharedPrefs.getBoolean(FIRST_LOAD_COMPLETE, false)) {
4125 String permission =
4126 getResources().getString(R.string.receive_first_load_broadcast_permission);
4127 Intent intent = new Intent(ACTION_FIRST_LOAD_COMPLETE);
4128 sendBroadcast(intent, permission);
4129 SharedPreferences.Editor editor = mSharedPrefs.edit();
4130 editor.putBoolean(FIRST_LOAD_COMPLETE, true);
4131 editor.apply();
4132 }
4133 }
4134
4135 public boolean isAllAppsButtonRank(int rank) {
4136 if (mHotseat != null) {
4137 return mHotseat.isAllAppsButtonRank(rank);
4138 }
4139 return false;
4140 }
4141
4142 private boolean canRunNewAppsAnimation() {
4143 long diff = System.currentTimeMillis() - mDragController.getLastGestureUpTime();
4144 return diff > (NEW_APPS_ANIMATION_INACTIVE_TIMEOUT_SECONDS * 1000);
4145 }
4146
4147 private ValueAnimator createNewAppBounceAnimation(View v, int i) {
4148 ValueAnimator bounceAnim = LauncherAnimUtils.ofPropertyValuesHolder(v,
4149 PropertyValuesHolder.ofFloat("alpha", 1f),
4150 PropertyValuesHolder.ofFloat("scaleX", 1f),
4151 PropertyValuesHolder.ofFloat("scaleY", 1f));
4152 bounceAnim.setDuration(InstallShortcutReceiver.NEW_SHORTCUT_BOUNCE_DURATION);
4153 bounceAnim.setStartDelay(i * InstallShortcutReceiver.NEW_SHORTCUT_STAGGER_DELAY);
4154 bounceAnim.setInterpolator(new SmoothPagedView.OvershootInterpolator());
4155 return bounceAnim;
4156 }
4157
4158 public boolean useVerticalBarLayout() {
4159 return LauncherAppState.getInstance().getDynamicGrid().
4160 getDeviceProfile().isVerticalBarLayout();
4161 }
4162
4163 protected Rect getSearchBarBounds() {
4164 return LauncherAppState.getInstance().getDynamicGrid().
4165 getDeviceProfile().getSearchBarBounds();
4166 }
4167
4168 public void bindSearchablesChanged() {
4169 if (mSearchDropTargetBar == null) {
4170 return;
4171 }
4172 if (mQsb != null) {
4173 mSearchDropTargetBar.removeView(mQsb);
4174 mQsb = null;
4175 }
4176 mSearchDropTargetBar.setQsbSearchBar(getOrCreateQsbBar());
4177 }
4178
4179 /**
4180 * Add the icons for all apps.
4181 *
4182 * Implementation of the method from LauncherModel.Callbacks.
4183 */
4184 public void bindAllApplications(final ArrayList<AppInfo> apps) {
4185 if (mAppsView != null) {
4186 mAppsView.setApps(apps);
4187 }
4188 if (mWidgetsView != null) {
4189 mWidgetsView.addWidgets(LauncherModel.getSortedWidgetsAndShortcuts(this, false),
4190 getPackageManager());
4191 }
4192 if (mLauncherCallbacks != null) {
4193 mLauncherCallbacks.bindAllApplications(apps);
4194 }
4195 }
4196
4197 /**
4198 * A package was updated.
4199 *
4200 * Implementation of the method from LauncherModel.Callbacks.
4201 */
4202 public void bindAppsUpdated(final ArrayList<AppInfo> apps) {
4203 Runnable r = new Runnable() {
4204 public void run() {
4205 bindAppsUpdated(apps);
4206 }
4207 };
4208 if (waitUntilResume(r)) {
4209 return;
4210 }
4211
4212 if (mAppsView != null) {
4213 mAppsView.updateApps(apps);
4214 }
4215 }
4216
4217 @Override
4218 public void bindWidgetsRestored(final ArrayList<LauncherAppWidgetInfo> widgets) {
4219 Runnable r = new Runnable() {
4220 public void run() {
4221 bindWidgetsRestored(widgets);
4222 }
4223 };
4224 if (waitUntilResume(r)) {
4225 return;
4226 }
4227 mWorkspace.widgetsRestored(widgets);
4228 }
4229
4230 /**
4231 * Some shortcuts were updated in the background.
4232 *
4233 * Implementation of the method from LauncherModel.Callbacks.
4234 */
4235 @Override
4236 public void bindShortcutsChanged(final ArrayList<ShortcutInfo> updated,
4237 final ArrayList<ShortcutInfo> removed, final UserHandleCompat user) {
4238 Runnable r = new Runnable() {
4239 public void run() {
4240 bindShortcutsChanged(updated, removed, user);
4241 }
4242 };
4243 if (waitUntilResume(r)) {
4244 return;
4245 }
4246
4247 if (!updated.isEmpty()) {
4248 mWorkspace.updateShortcuts(updated);
4249 }
4250
4251 if (!removed.isEmpty()) {
4252 HashSet<ComponentName> removedComponents = new HashSet<ComponentName>();
4253 for (ShortcutInfo si : removed) {
4254 removedComponents.add(si.getTargetComponent());
4255 }
4256 mWorkspace.removeItemsByComponentName(removedComponents, user);
4257 // Notify the drag controller
4258 mDragController.onAppsRemoved(new ArrayList<String>(), removedComponents);
4259 }
4260 }
4261
4262 /**
4263 * Update the state of a package, typically related to install state.
4264 *
4265 * Implementation of the method from LauncherModel.Callbacks.
4266 */
4267 @Override
4268 public void updatePackageState(ArrayList<PackageInstallInfo> installInfo) {
4269 if (mWorkspace != null) {
4270 mWorkspace.updatePackageState(installInfo);
4271 }
4272 }
4273
4274 /**
4275 * Update the label and icon of all the icons in a package
4276 *
4277 * Implementation of the method from LauncherModel.Callbacks.
4278 */
4279 @Override
4280 public void updatePackageBadge(String packageName) {
4281 if (mWorkspace != null) {
4282 mWorkspace.updatePackageBadge(packageName, UserHandleCompat.myUserHandle());
4283 }
4284 }
4285
4286 /**
4287 * A package was uninstalled. We take both the super set of packageNames
4288 * in addition to specific applications to remove, the reason being that
4289 * this can be called when a package is updated as well. In that scenario,
4290 * we only remove specific components from the workspace, where as
4291 * package-removal should clear all items by package name.
4292 *
4293 * @param reason if non-zero, the icons are not permanently removed, rather marked as disabled.
4294 * Implementation of the method from LauncherModel.Callbacks.
4295 */
4296 @Override
4297 public void bindComponentsRemoved(final ArrayList<String> packageNames,
4298 final ArrayList<AppInfo> appInfos, final UserHandleCompat user, final int reason) {
4299 Runnable r = new Runnable() {
4300 public void run() {
4301 bindComponentsRemoved(packageNames, appInfos, user, reason);
4302 }
4303 };
4304 if (waitUntilResume(r)) {
4305 return;
4306 }
4307
4308 if (reason == 0) {
4309 HashSet<ComponentName> removedComponents = new HashSet<ComponentName>();
4310 for (AppInfo info : appInfos) {
4311 removedComponents.add(info.componentName);
4312 }
4313 if (!packageNames.isEmpty()) {
4314 mWorkspace.removeItemsByPackageName(packageNames, user);
4315 }
4316 if (!removedComponents.isEmpty()) {
4317 mWorkspace.removeItemsByComponentName(removedComponents, user);
4318 }
4319 // Notify the drag controller
4320 mDragController.onAppsRemoved(packageNames, removedComponents);
4321
4322 } else {
4323 mWorkspace.disableShortcutsByPackageName(packageNames, user, reason);
4324 }
4325
4326 // Update AllApps
4327 if (mAppsView != null) {
4328 mAppsView.removeApps(appInfos);
4329 }
4330 }
4331
4332 /**
4333 * A number of packages were updated.
4334 */
4335 @Thunk ArrayList<Object> mWidgetsAndShortcuts;
4336 private Runnable mBindPackagesUpdatedRunnable = new Runnable() {
4337 public void run() {
4338 bindPackagesUpdated(mWidgetsAndShortcuts);
4339 mWidgetsAndShortcuts = null;
4340 }
4341 };
4342
4343 public void bindPackagesUpdated(final ArrayList<Object> widgetsAndShortcuts) {
4344 if (waitUntilResume(mBindPackagesUpdatedRunnable, true)) {
4345 mWidgetsAndShortcuts = widgetsAndShortcuts;
4346 return;
4347 }
4348
4349 if (mWidgetsView != null) {
4350 mWidgetsView.addWidgets(LauncherModel.getSortedWidgetsAndShortcuts(this, false),
4351 getPackageManager());
4352 }
4353 }
4354
4355 private int mapConfigurationOriActivityInfoOri(int configOri) {
4356 final Display d = getWindowManager().getDefaultDisplay();
4357 int naturalOri = Configuration.ORIENTATION_LANDSCAPE;
4358 switch (d.getRotation()) {
4359 case Surface.ROTATION_0:
4360 case Surface.ROTATION_180:
4361 // We are currently in the same basic orientation as the natural orientation
4362 naturalOri = configOri;
4363 break;
4364 case Surface.ROTATION_90:
4365 case Surface.ROTATION_270:
4366 // We are currently in the other basic orientation to the natural orientation
4367 naturalOri = (configOri == Configuration.ORIENTATION_LANDSCAPE) ?
4368 Configuration.ORIENTATION_PORTRAIT : Configuration.ORIENTATION_LANDSCAPE;
4369 break;
4370 }
4371
4372 int[] oriMap = {
4373 ActivityInfo.SCREEN_ORIENTATION_PORTRAIT,
4374 ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE,
4375 ActivityInfo.SCREEN_ORIENTATION_REVERSE_PORTRAIT,
4376 ActivityInfo.SCREEN_ORIENTATION_REVERSE_LANDSCAPE
4377 };
4378 // Since the map starts at portrait, we need to offset if this device's natural orientation
4379 // is landscape.
4380 int indexOffset = 0;
4381 if (naturalOri == Configuration.ORIENTATION_LANDSCAPE) {
4382 indexOffset = 1;
4383 }
4384 return oriMap[(d.getRotation() + indexOffset) % 4];
4385 }
4386
4387 public void lockScreenOrientation() {
4388 if (Utilities.isRotationEnabled(this)) {
4389 if (Build.VERSION.SDK_INT < Build.VERSION_CODES.JELLY_BEAN_MR2) {
4390 setRequestedOrientation(mapConfigurationOriActivityInfoOri(getResources()
4391 .getConfiguration().orientation));
4392 } else {
4393 setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_LOCKED);
4394 }
4395 }
4396 }
4397 public void unlockScreenOrientation(boolean immediate) {
4398 if (Utilities.isRotationEnabled(this)) {
4399 if (immediate) {
4400 setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED);
4401 } else {
4402 mHandler.postDelayed(new Runnable() {
4403 public void run() {
4404 setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED);
4405 }
4406 }, mRestoreScreenOrientationDelay);
4407 }
4408 }
4409 }
4410
4411 protected boolean isLauncherPreinstalled() {
4412 if (mLauncherCallbacks != null) {
4413 return mLauncherCallbacks.isLauncherPreinstalled();
4414 }
4415 PackageManager pm = getPackageManager();
4416 try {
4417 ApplicationInfo ai = pm.getApplicationInfo(getComponentName().getPackageName(), 0);
4418 if ((ai.flags & ApplicationInfo.FLAG_SYSTEM) != 0) {
4419 return true;
4420 } else {
4421 return false;
4422 }
4423 } catch (NameNotFoundException e) {
4424 e.printStackTrace();
4425 return false;
4426 }
4427 }
4428
4429 /**
4430 * This method indicates whether or not we should suggest default wallpaper dimensions
4431 * when our wallpaper cropper was not yet used to set a wallpaper.
4432 */
4433 protected boolean overrideWallpaperDimensions() {
4434 if (mLauncherCallbacks != null) {
4435 return mLauncherCallbacks.overrideWallpaperDimensions();
4436 }
4437 return true;
4438 }
4439
4440 /**
4441 * To be overridden by subclasses to indicate that there is an activity to launch
4442 * before showing the standard launcher experience.
4443 */
4444 protected boolean hasFirstRunActivity() {
4445 if (mLauncherCallbacks != null) {
4446 return mLauncherCallbacks.hasFirstRunActivity();
4447 }
4448 return false;
4449 }
4450
4451 /**
4452 * To be overridden by subclasses to launch any first run activity
4453 */
4454 protected Intent getFirstRunActivity() {
4455 if (mLauncherCallbacks != null) {
4456 return mLauncherCallbacks.getFirstRunActivity();
4457 }
4458 return null;
4459 }
4460
4461 /**
4462 * Returns whether the launcher callbacks overrides search in all apps.
4463 * @return
4464 */
4465 @Thunk boolean isAllAppsSearchOverridden() {
4466 if (mLauncherCallbacks != null) {
4467 return mLauncherCallbacks.overrideAllAppsSearch();
4468 }
4469 return false;
4470 }
4471
4472 private boolean shouldRunFirstRunActivity() {
4473 return !ActivityManager.isRunningInTestHarness() &&
4474 !mSharedPrefs.getBoolean(FIRST_RUN_ACTIVITY_DISPLAYED, false);
4475 }
4476
4477 protected boolean hasRunFirstRunActivity() {
4478 return mSharedPrefs.getBoolean(FIRST_RUN_ACTIVITY_DISPLAYED, false);
4479 }
4480
4481 public boolean showFirstRunActivity() {
4482 if (shouldRunFirstRunActivity() &&
4483 hasFirstRunActivity()) {
4484 Intent firstRunIntent = getFirstRunActivity();
4485 if (firstRunIntent != null) {
4486 startActivity(firstRunIntent);
4487 markFirstRunActivityShown();
4488 return true;
4489 }
4490 }
4491 return false;
4492 }
4493
4494 private void markFirstRunActivityShown() {
4495 SharedPreferences.Editor editor = mSharedPrefs.edit();
4496 editor.putBoolean(FIRST_RUN_ACTIVITY_DISPLAYED, true);
4497 editor.apply();
4498 }
4499
4500 /**
4501 * To be overridden by subclasses to indicate that there is an in-activity full-screen intro
4502 * screen that must be displayed and dismissed.
4503 */
4504 protected boolean hasDismissableIntroScreen() {
4505 if (mLauncherCallbacks != null) {
4506 return mLauncherCallbacks.hasDismissableIntroScreen();
4507 }
4508 return false;
4509 }
4510
4511 /**
4512 * Full screen intro screen to be shown and dismissed before the launcher can be used.
4513 */
4514 protected View getIntroScreen() {
4515 if (mLauncherCallbacks != null) {
4516 return mLauncherCallbacks.getIntroScreen();
4517 }
4518 return null;
4519 }
4520
4521 /**
4522 * To be overriden by subclasses to indicate whether the in-activity intro screen has been
4523 * dismissed. This method is ignored if #hasDismissableIntroScreen returns false.
4524 */
4525 private boolean shouldShowIntroScreen() {
4526 return hasDismissableIntroScreen() &&
4527 !mSharedPrefs.getBoolean(INTRO_SCREEN_DISMISSED, false);
4528 }
4529
4530 protected void showIntroScreen() {
4531 View introScreen = getIntroScreen();
4532 changeWallpaperVisiblity(false);
4533 if (introScreen != null) {
4534 mDragLayer.showOverlayView(introScreen);
4535 }
4536 if (mLauncherOverlayContainer != null) {
4537 mLauncherOverlayContainer.setVisibility(View.INVISIBLE);
4538 }
4539 }
4540
4541 public void dismissIntroScreen() {
4542 markIntroScreenDismissed();
4543 if (showFirstRunActivity()) {
4544 // We delay hiding the intro view until the first run activity is showing. This
4545 // avoids a blip.
4546 mWorkspace.postDelayed(new Runnable() {
4547 @Override
4548 public void run() {
4549 mDragLayer.dismissOverlayView();
4550 if (mLauncherOverlayContainer != null) {
4551 mLauncherOverlayContainer.setVisibility(View.VISIBLE);
4552 }
4553 showFirstRunClings();
4554 }
4555 }, ACTIVITY_START_DELAY);
4556 } else {
4557 mDragLayer.dismissOverlayView();
4558 if (mLauncherOverlayContainer != null) {
4559 mLauncherOverlayContainer.setVisibility(View.VISIBLE);
4560 }
4561 showFirstRunClings();
4562 }
4563 changeWallpaperVisiblity(true);
4564 }
4565
4566 private void markIntroScreenDismissed() {
4567 SharedPreferences.Editor editor = mSharedPrefs.edit();
4568 editor.putBoolean(INTRO_SCREEN_DISMISSED, true);
4569 editor.apply();
4570 }
4571
4572 @Thunk void showFirstRunClings() {
4573 // The two first run cling paths are mutually exclusive, if the launcher is preinstalled
4574 // on the device, then we always show the first run cling experience (or if there is no
4575 // launcher2). Otherwise, we prompt the user upon started for migration
4576 LauncherClings launcherClings = new LauncherClings(this);
4577 if (launcherClings.shouldShowFirstRunOrMigrationClings()) {
4578 if (mModel.canMigrateFromOldLauncherDb(this)) {
4579 launcherClings.showMigrationCling();
4580 } else {
4581 launcherClings.showLongPressCling(true);
4582 }
4583 }
4584 }
4585
4586 void showWorkspaceSearchAndHotseat() {
4587 if (mWorkspace != null) mWorkspace.setAlpha(1f);
4588 if (mHotseat != null) mHotseat.setAlpha(1f);
4589 if (mPageIndicators != null) mPageIndicators.setAlpha(1f);
4590 if (mSearchDropTargetBar != null) mSearchDropTargetBar.showSearchBar(false);
4591 }
4592
4593 void hideWorkspaceSearchAndHotseat() {
4594 if (mWorkspace != null) mWorkspace.setAlpha(0f);
4595 if (mHotseat != null) mHotseat.setAlpha(0f);
4596 if (mPageIndicators != null) mPageIndicators.setAlpha(0f);
4597 if (mSearchDropTargetBar != null) mSearchDropTargetBar.hideSearchBar(false);
4598 }
4599
4600 public ItemInfo createAppDragInfo(Intent appLaunchIntent) {
4601 // Called from search suggestion, not supported in other profiles.
4602 final UserHandleCompat myUser = UserHandleCompat.myUserHandle();
4603 LauncherAppsCompat launcherApps = LauncherAppsCompat.getInstance(this);
4604 LauncherActivityInfoCompat activityInfo = launcherApps.resolveActivity(appLaunchIntent,
4605 myUser);
4606 if (activityInfo == null) {
4607 return null;
4608 }
4609 return new AppInfo(this, activityInfo, myUser, mIconCache);
4610 }
4611
4612 public ItemInfo createShortcutDragInfo(Intent shortcutIntent, CharSequence caption,
4613 Bitmap icon) {
4614 // Called from search suggestion, not supported in other profiles.
4615 return createShortcutDragInfo(shortcutIntent, caption, icon,
4616 UserHandleCompat.myUserHandle());
4617 }
4618
4619 public ItemInfo createShortcutDragInfo(Intent shortcutIntent, CharSequence caption,
4620 Bitmap icon, UserHandleCompat user) {
4621 UserManagerCompat userManager = UserManagerCompat.getInstance(this);
4622 CharSequence contentDescription = userManager.getBadgedLabelForUser(caption, user);
4623 return new ShortcutInfo(shortcutIntent, caption, contentDescription, icon, user);
4624 }
4625
4626 protected void moveWorkspaceToDefaultScreen() {
4627 mWorkspace.moveToDefaultScreen(false);
4628 }
4629
4630 public void startDrag(View dragView, ItemInfo dragInfo, DragSource source) {
4631 dragView.setTag(dragInfo);
4632 mWorkspace.onExternalDragStartedWithItem(dragView);
4633 mWorkspace.beginExternalDragShared(dragView, source);
4634 }
4635
4636 @Override
4637 public void onPageSwitch(View newPage, int newPageIndex) {
4638 if (mLauncherCallbacks != null) {
4639 mLauncherCallbacks.onPageSwitch(newPage, newPageIndex);
4640 }
4641 }
4642
4643 /**
4644 * Prints out out state for debugging.
4645 */
4646 public void dumpState() {
4647 Log.d(TAG, "BEGIN launcher3 dump state for launcher " + this);
4648 Log.d(TAG, "mSavedState=" + mSavedState);
4649 Log.d(TAG, "mWorkspaceLoading=" + mWorkspaceLoading);
4650 Log.d(TAG, "mRestoring=" + mRestoring);
4651 Log.d(TAG, "mWaitingForResult=" + mWaitingForResult);
4652 Log.d(TAG, "mSavedInstanceState=" + mSavedInstanceState);
4653 Log.d(TAG, "sFolders.size=" + sFolders.size());
4654 mModel.dumpState();
4655 // TODO(hyunyoungs): add mWidgetsView.dumpState(); or mWidgetsModel.dumpState();
4656
4657 Log.d(TAG, "END launcher3 dump state");
4658 }
4659
4660 @Override
4661 public void dump(String prefix, FileDescriptor fd, PrintWriter writer, String[] args) {
4662 super.dump(prefix, fd, writer, args);
4663 synchronized (sDumpLogs) {
4664 writer.println(" ");
4665 writer.println("Debug logs: ");
4666 for (int i = 0; i < sDumpLogs.size(); i++) {
4667 writer.println(" " + sDumpLogs.get(i));
4668 }
4669 }
4670 if (mLauncherCallbacks != null) {
4671 mLauncherCallbacks.dump(prefix, fd, writer, args);
4672 }
4673 }
4674
4675 public static void dumpDebugLogsToConsole() {
4676 if (DEBUG_DUMP_LOG) {
4677 synchronized (sDumpLogs) {
4678 Log.d(TAG, "");
4679 Log.d(TAG, "*********************");
4680 Log.d(TAG, "Launcher debug logs: ");
4681 for (int i = 0; i < sDumpLogs.size(); i++) {
4682 Log.d(TAG, " " + sDumpLogs.get(i));
4683 }
4684 Log.d(TAG, "*********************");
4685 Log.d(TAG, "");
4686 }
4687 }
4688 }
4689
4690 public static void addDumpLog(String tag, String log, boolean debugLog) {
4691 addDumpLog(tag, log, null, debugLog);
4692 }
4693
4694 public static void addDumpLog(String tag, String log, Exception e, boolean debugLog) {
4695 if (debugLog) {
4696 if (e != null) {
4697 Log.d(tag, log, e);
4698 } else {
4699 Log.d(tag, log);
4700 }
4701 }
4702 if (DEBUG_DUMP_LOG) {
4703 sDateStamp.setTime(System.currentTimeMillis());
4704 synchronized (sDumpLogs) {
4705 sDumpLogs.add(sDateFormat.format(sDateStamp) + ": " + tag + ", " + log
4706 + (e == null ? "" : (", Exception: " + e)));
4707 }
4708 }
4709 }
4710
4711 public static CustomAppWidget getCustomAppWidget(String name) {
4712 return sCustomAppWidgets.get(name);
4713 }
4714
4715 public static HashMap<String, CustomAppWidget> getCustomAppWidgets() {
4716 return sCustomAppWidgets;
4717 }
4718
4719 public void dumpLogsToLocalData() {
4720 if (DEBUG_DUMP_LOG) {
4721 new AsyncTask<Void, Void, Void>() {
4722 public Void doInBackground(Void ... args) {
4723 boolean success = false;
4724 sDateStamp.setTime(sRunStart);
4725 String FILENAME = sDateStamp.getMonth() + "-"
4726 + sDateStamp.getDay() + "_"
4727 + sDateStamp.getHours() + "-"
4728 + sDateStamp.getMinutes() + "_"
4729 + sDateStamp.getSeconds() + ".txt";
4730
4731 FileOutputStream fos = null;
4732 File outFile = null;
4733 try {
4734 outFile = new File(getFilesDir(), FILENAME);
4735 outFile.createNewFile();
4736 fos = new FileOutputStream(outFile);
4737 } catch (Exception e) {
4738 e.printStackTrace();
4739 }
4740 if (fos != null) {
4741 PrintWriter writer = new PrintWriter(fos);
4742
4743 writer.println(" ");
4744 writer.println("Debug logs: ");
4745 synchronized (sDumpLogs) {
4746 for (int i = 0; i < sDumpLogs.size(); i++) {
4747 writer.println(" " + sDumpLogs.get(i));
4748 }
4749 }
4750 writer.close();
4751 }
4752 try {
4753 if (fos != null) {
4754 fos.close();
4755 success = true;
4756 }
4757 } catch (IOException e) {
4758 e.printStackTrace();
4759 }
4760 return null;
4761 }
4762 }.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR, (Void) null);
4763 }
4764 }
4765 }
4766
4767 interface LauncherTransitionable {
4768 View getContent();
4769 void onLauncherTransitionPrepare(Launcher l, boolean animated, boolean toWorkspace);
4770 void onLauncherTransitionStart(Launcher l, boolean animated, boolean toWorkspace);
4771 void onLauncherTransitionStep(Launcher l, float t);
4772 void onLauncherTransitionEnd(Launcher l, boolean animated, boolean toWorkspace);
4773 }
4774
4775 interface DebugIntents {
4776 static final String DELETE_DATABASE = "com.android.launcher3.action.DELETE_DATABASE";
4777 static final String MIGRATE_DATABASE = "com.android.launcher3.action.MIGRATE_DATABASE";
4778 } |
1 /*
2 * Copyright (C) 2008 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 * http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17 package com.android.launcher3;
18
19 import android.animation.Animator;
20 import android.animation.AnimatorListenerAdapter;
21 import android.animation.AnimatorSet;
22 import android.animation.ObjectAnimator;
23 import android.animation.PropertyValuesHolder;
24 import android.animation.ValueAnimator;
25 import android.annotation.TargetApi;
26 import android.app.Activity;
27 import android.app.ActivityManager;
28 import android.app.ActivityOptions;
29 import android.app.AlertDialog;
30 import android.app.SearchManager;
31 import android.appwidget.AppWidgetHostView;
32 import android.appwidget.AppWidgetManager;
33 import android.appwidget.AppWidgetProviderInfo;
34 import android.content.ActivityNotFoundException;
35 import android.content.BroadcastReceiver;
36 import android.content.ComponentCallbacks2;
37 import android.content.ComponentName;
38 import android.content.ContentResolver;
39 import android.content.Context;
40 import android.content.DialogInterface;
41 import android.content.Intent;
42 import android.content.IntentFilter;
43 import android.content.IntentSender;
44 import android.content.SharedPreferences;
45 import android.content.pm.ActivityInfo;
46 import android.content.pm.ApplicationInfo;
47 import android.content.pm.PackageManager;
48 import android.content.pm.PackageManager.NameNotFoundException;
49 import android.content.res.Configuration;
50 import android.database.ContentObserver;
51 import android.database.sqlite.SQLiteDatabase;
52 import android.graphics.Bitmap;
53 import android.graphics.Canvas;
54 import android.graphics.Color;
55 import android.graphics.PorterDuff;
56 import android.graphics.Rect;
57 import android.graphics.drawable.Drawable;
58 import android.net.Uri;
59 import android.os.AsyncTask;
60 import android.os.Build;
61 import android.os.Bundle;
62 import android.os.Environment;
63 import android.os.Handler;
64 import android.os.Message;
65 import android.os.StrictMode;
66 import android.os.SystemClock;
67 import android.text.Selection;
68 import android.text.SpannableStringBuilder;
69 import android.text.TextUtils;
70 import android.text.method.TextKeyListener;
71 import android.util.Log;
72 import android.view.Display;
73 import android.view.Gravity;
74 import android.view.HapticFeedbackConstants;
75 import android.view.KeyEvent;
76 import android.view.LayoutInflater;
77 import android.view.Menu;
78 import android.view.MotionEvent;
79 import android.view.Surface;
80 import android.view.View;
81 import android.view.View.OnClickListener;
82 import android.view.View.OnLongClickListener;
83 import android.view.ViewGroup;
84 import android.view.ViewStub;
85 import android.view.ViewTreeObserver;
86 import android.view.Window;
87 import android.view.WindowManager;
88 import android.view.accessibility.AccessibilityEvent;
89 import android.view.inputmethod.InputMethodManager;
90 import android.widget.Advanceable;
91 import android.widget.FrameLayout;
92 import android.widget.ImageView;
93 import android.widget.TextView;
94 import android.widget.Toast;
95
96 import com.android.launcher3.DropTarget.DragObject;
97 import com.android.launcher3.PagedView.PageSwitchListener;
98 import com.android.launcher3.compat.AppWidgetManagerCompat;
99 import com.android.launcher3.compat.LauncherActivityInfoCompat;
100 import com.android.launcher3.compat.LauncherAppsCompat;
101 import com.android.launcher3.compat.PackageInstallerCompat;
102 import com.android.launcher3.compat.PackageInstallerCompat.PackageInstallInfo;
103 import com.android.launcher3.compat.UserHandleCompat;
104 import com.android.launcher3.compat.UserManagerCompat;
105 import com.android.launcher3.util.Thunk;
106 import com.android.launcher3.widget.PendingAddWidgetInfo;
107 import com.android.launcher3.widget.WidgetsContainerView;
108
109 import java.io.DataInputStream;
110 import java.io.DataOutputStream;
111 import java.io.File;
112 import java.io.FileDescriptor;
113 import java.io.FileNotFoundException;
114 import java.io.FileOutputStream;
115 import java.io.IOException;
116 import java.io.PrintWriter;
117 import java.lang.reflect.InvocationTargetException;
118 import java.lang.reflect.Method;
119 import java.text.DateFormat;
120 import java.util.ArrayList;
121 import java.util.Collection;
122 import java.util.Date;
123 import java.util.HashMap;
124 import java.util.HashSet;
125 import java.util.List;
126 import java.util.concurrent.atomic.AtomicInteger;
127
128 /**
129 * Default launcher application.
130 */
131 public class Launcher extends Activity
132 implements View.OnClickListener, OnLongClickListener, LauncherModel.Callbacks,
133 View.OnTouchListener, PageSwitchListener, LauncherProviderChangeListener,
134 LauncherStateTransitionAnimation.Callbacks {
135 static final String TAG = "Launcher";
136 static final boolean LOGD = true;
137
138 static final boolean PROFILE_STARTUP = false;
139 static final boolean DEBUG_WIDGETS = true;
140 static final boolean DEBUG_STRICT_MODE = false;
141 static final boolean DEBUG_RESUME_TIME = false;
142 static final boolean DEBUG_DUMP_LOG = false;
143
144 static final boolean ENABLE_DEBUG_INTENTS = false; // allow DebugIntents to run
145
146 private static final int REQUEST_CREATE_SHORTCUT = 1;
147 private static final int REQUEST_CREATE_APPWIDGET = 5;
148 private static final int REQUEST_PICK_APPWIDGET = 9;
149 private static final int REQUEST_PICK_WALLPAPER = 10;
150
151 private static final int REQUEST_BIND_APPWIDGET = 11;
152 private static final int REQUEST_RECONFIGURE_APPWIDGET = 12;
153
154 /**
155 * IntentStarter uses request codes starting with this. This must be greater than all activity
156 * request codes used internally.
157 */
158 protected static final int REQUEST_LAST = 100;
159
160 static final String EXTRA_SHORTCUT_DUPLICATE = "duplicate";
161
162 static final int SCREEN_COUNT = 5;
163
164 // To turn on these properties, type
165 // adb shell setprop log.tag.PROPERTY_NAME [VERBOSE | SUPPRESS]
166 static final String DUMP_STATE_PROPERTY = "launcher_dump_state";
167
168 // The Intent extra that defines whether to ignore the launch animation
169 static final String INTENT_EXTRA_IGNORE_LAUNCH_ANIMATION =
170 "com.android.launcher3.intent.extra.shortcut.INGORE_LAUNCH_ANIMATION";
171
172 // Type: int
173 private static final String RUNTIME_STATE_CURRENT_SCREEN = "launcher.current_screen";
174 // Type: int
175 private static final String RUNTIME_STATE = "launcher.state";
176 // Type: int
177 private static final String RUNTIME_STATE_PENDING_ADD_CONTAINER = "launcher.add_container";
178 // Type: int
179 private static final String RUNTIME_STATE_PENDING_ADD_SCREEN = "launcher.add_screen";
180 // Type: int
181 private static final String RUNTIME_STATE_PENDING_ADD_CELL_X = "launcher.add_cell_x";
182 // Type: int
183 private static final String RUNTIME_STATE_PENDING_ADD_CELL_Y = "launcher.add_cell_y";
184 // Type: boolean
185 private static final String RUNTIME_STATE_PENDING_FOLDER_RENAME = "launcher.rename_folder";
186 // Type: long
187 private static final String RUNTIME_STATE_PENDING_FOLDER_RENAME_ID = "launcher.rename_folder_id";
188 // Type: int
189 private static final String RUNTIME_STATE_PENDING_ADD_SPAN_X = "launcher.add_span_x";
190 // Type: int
191 private static final String RUNTIME_STATE_PENDING_ADD_SPAN_Y = "launcher.add_span_y";
192 // Type: parcelable
193 private static final String RUNTIME_STATE_PENDING_ADD_WIDGET_INFO = "launcher.add_widget_info";
194 // Type: parcelable
195 private static final String RUNTIME_STATE_PENDING_ADD_WIDGET_ID = "launcher.add_widget_id";
196 // Type: int[]
197 private static final String RUNTIME_STATE_VIEW_IDS = "launcher.view_ids";
198
199 static final String INTRO_SCREEN_DISMISSED = "launcher.intro_screen_dismissed";
200 static final String FIRST_RUN_ACTIVITY_DISPLAYED = "launcher.first_run_activity_displayed";
201
202 static final String FIRST_LOAD_COMPLETE = "launcher.first_load_complete";
203 static final String ACTION_FIRST_LOAD_COMPLETE =
204 "com.android.launcher3.action.FIRST_LOAD_COMPLETE";
205
206 public static final String SHOW_WEIGHT_WATCHER = "debug.show_mem";
207 public static final boolean SHOW_WEIGHT_WATCHER_DEFAULT = false;
208
209 private static final String QSB_WIDGET_ID = "qsb_widget_id";
210 private static final String QSB_WIDGET_PROVIDER = "qsb_widget_provider";
211
212 public static final String USER_HAS_MIGRATED = "launcher.user_migrated_from_old_data";
213
214 /** The different states that Launcher can be in. */
215
216
217 /** The different states that Launcher can be in. */
218 enum State { NONE, WORKSPACE, APPS, APPS_SPRING_LOADED, WIDGETS, WIDGETS_SPRING_LOADED,, };;
219 @Thunk State mState = State.WORKSPACE;
220 @Thunk AnimatorSet mStateAnimation;
221 @Thunk LauncherStateTransitionAnimation mStateTransitionAnimation;
222
223 private boolean mIsSafeModeEnabled;
224
225 LauncherOverlayCallbacks mLauncherOverlayCallbacks = new LauncherOverlayCallbacksImpl();
226 LauncherOverlay mLauncherOverlay;
227 InsettableFrameLayout mLauncherOverlayContainer;
228
229 static final int APPWIDGET_HOST_ID = 1024;
230 public static final int EXIT_SPRINGLOADED_MODE_SHORT_TIMEOUT = 300;
231 private static final int ON_ACTIVITY_RESULT_ANIMATION_DELAY = 500;
232 private static final int ACTIVITY_START_DELAY = 1000;
233
234 private HashMap<Integer, Integer> mItemIdToViewId = new HashMap<Integer, Integer>();
235 private static final AtomicInteger sNextGeneratedId = new AtomicInteger(1);
236
237 // How long to wait before the new-shortcut animation automatically pans the workspace
238 private static int NEW_APPS_PAGE_MOVE_DELAY = 500;
239 private static int NEW_APPS_ANIMATION_INACTIVE_TIMEOUT_SECONDS = 5;
240 @Thunk static int NEW_APPS_ANIMATION_DELAY = 500;
241
242 private final BroadcastReceiver mCloseSystemDialogsReceiver
243 = new CloseSystemDialogsIntentReceiver();
244 private final ContentObserver mWidgetObserver = new AppWidgetResetObserver();
245
246 private LayoutInflater mInflater;
247
248 @Thunk Workspace mWorkspace;
249 private View mLauncherView;
250 private View mPageIndicators;
251 @Thunk DragLayer mDragLayer;
252 private DragController mDragController;
253 private View mWeightWatcher;
254
255 private AppWidgetManagerCompat mAppWidgetManager;
256 private LauncherAppWidgetHost mAppWidgetHost;
257
258 @Thunk ItemInfo mPendingAddInfo = new ItemInfo();
259 private LauncherAppWidgetProviderInfo mPendingAddWidgetInfo;
260 private int mPendingAddWidgetId = -1;
261
262 private int[] mTmpAddItemCellCoordinates = new int[2];
263
264 private FolderInfo mFolderInfo;
265
266 private Hotseat mHotseat;
267 private ViewGroup mOverviewPanel;
268
269 private View mAllAppsButton;
270
271 private SearchDropTargetBar mSearchDropTargetBar;
272
273 // Main container view for the all apps screen.
274 @Thunk AppsContainerView mAppsView;
275
276 // Main container view for the widget tray screen.
277 private WidgetsContainerView mWidgetsView;
278
279 private boolean mAutoAdvanceRunning = false;
280 private AppWidgetHostView mQsb;
281
282 private Bundle mSavedState;
283 // We set the state in both onCreate and then onNewIntent in some cases, which causes both
284 // scroll issues (because the workspace may not have been measured yet) and extra work.
285 // Instead, just save the state that we need to restore Launcher to, and commit it in onResume.
286 private State mOnResumeState = State.NONE;
287
288 private SpannableStringBuilder mDefaultKeySsb = null;
289
290 @Thunk boolean mWorkspaceLoading = true;
291
292 private boolean mPaused = true;
293 private boolean mRestoring;
294 private boolean mWaitingForResult;
295 private boolean mOnResumeNeedsLoad;
296
297 private ArrayList<Runnable> mBindOnResumeCallbacks = new ArrayList<Runnable>();
298 private ArrayList<Runnable> mOnResumeCallbacks = new ArrayList<Runnable>();
299
300 private Bundle mSavedInstanceState;
301
302 private LauncherModel mModel;
303 private IconCache mIconCache;
304 @Thunk boolean mUserPresent = true;
305 private boolean mVisible = false;
306 private boolean mHasFocus = false;
307 private boolean mAttached = false;
308
309 @Thunk static LocaleConfiguration sLocaleConfiguration = null;
310
311 private static HashMap<Long, FolderInfo> sFolders = new HashMap<Long, FolderInfo>();
312
313 private View.OnTouchListener mHapticFeedbackTouchListener;
314
315 // Related to the auto-advancing of widgets
316 private final int ADVANCE_MSG = 1;
317 private final int mAdvanceInterval = 20000;
318 private final int mAdvanceStagger = 250;
319 private long mAutoAdvanceSentTime;
320 private long mAutoAdvanceTimeLeft = -1;
321 @Thunk HashMap<View, AppWidgetProviderInfo> mWidgetsToAdvance =
322 new HashMap<View, AppWidgetProviderInfo>();
323
324 // Determines how long to wait after a rotation before restoring the screen orientation to
325 // match the sensor state.
326 private final int mRestoreScreenOrientationDelay = 500;
327
328 @Thunk Drawable mWorkspaceBackgroundDrawable;
329
330 private final ArrayList<Integer> mSynchronouslyBoundPages = new ArrayList<Integer>();
331 private static final boolean DISABLE_SYNCHRONOUS_BINDING_CURRENT_PAGE = false;
332
333 static final ArrayList<String> sDumpLogs = new ArrayList<String>();
334 static Date sDateStamp = new Date();
335 static DateFormat sDateFormat =
336 DateFormat.getDateTimeInstance(DateFormat.SHORT, DateFormat.SHORT);
337 static long sRunStart = System.currentTimeMillis();
338 static final String CORRUPTION_EMAIL_SENT_KEY = "corruptionEmailSent";
339
340 // We only want to get the SharedPreferences once since it does an FS stat each time we get
341 // it from the context.
342 private SharedPreferences mSharedPrefs;
343
344 // Holds the page that we need to animate to, and the icon views that we need to animate up
345 // when we scroll to that page on resume.
346 @Thunk ImageView mFolderIconImageView;
347 private Bitmap mFolderIconBitmap;
348 private Canvas mFolderIconCanvas;
349 private Rect mRectForFolderAnimation = new Rect();
350
351 private BubbleTextView mWaitingForResume;
352
353 protected static HashMap<String, CustomAppWidget> sCustomAppWidgets =
354 new HashMap<String, CustomAppWidget>();
355
356 private static final boolean ENABLE_CUSTOM_WIDGET_TEST = false;
357
358 // TODO: remove this field and call method directly when Launcher3 can depend on M APIs
359 private static Method sClipRevealMethod = null;
360 static {
361 Class<?> activityOptionsClass = ActivityOptions.class;
362 try {
363 sClipRevealMethod = activityOptionsClass.getDeclaredMethod("makeClipRevealAnimation",
364 View.class, int.class, int.class, int.class, int.class);
365 } catch (Exception e) {
366 // Earlier version
367 }
368 }
369 static {
370 if (ENABLE_CUSTOM_WIDGET_TEST) {
371 sCustomAppWidgets.put(DummyWidget.class.getName(), new DummyWidget());
372 }
373 }
374
375 @Thunk Runnable mBuildLayersRunnable = new Runnable() {
376 public void run() {
377 if (mWorkspace != null) {
378 mWorkspace.buildPageHardwareLayers();
379 }
380 }
381 };
382
383 private static PendingAddArguments sPendingAddItem;
384
385 @Thunk static class PendingAddArguments {
386 int requestCode;
387 Intent intent;
388 long container;
389 long screenId;
390 int cellX;
391 int cellY;
392 int appWidgetId;
393 }
394
395 private Stats mStats;
396
397 FocusIndicatorView mFocusHandler;
398
399 @Override
400 protected void onCreate(Bundle savedInstanceState) {
401 if (DEBUG_STRICT_MODE) {
402 StrictMode.setThreadPolicy(new StrictMode.ThreadPolicy.Builder()
403 .detectDiskReads()
404 .detectDiskWrites()
405 .detectNetwork() // or .detectAll() for all detectable problems
406 .penaltyLog()
407 .build());
408 StrictMode.setVmPolicy(new StrictMode.VmPolicy.Builder()
409 .detectLeakedSqlLiteObjects()
410 .detectLeakedClosableObjects()
411 .penaltyLog()
412 .penaltyDeath()
413 .build());
414 }
415
416 if (mLauncherCallbacks != null) {
417 mLauncherCallbacks.preOnCreate();
418 }
419
420 super.onCreate(savedInstanceState);
421
422 LauncherAppState.setApplicationContext(getApplicationContext());
423 LauncherAppState app = LauncherAppState.getInstance();
424 LauncherAppState.getLauncherProvider().setLauncherProviderChangeListener(this);
425
426 // Lazy-initialize the dynamic grid
427 DeviceProfile grid = app.initDynamicGrid(this);
428
429 // the LauncherApplication should call this, but in case of Instrumentation it might not be prese🔵
430 mSharedPrefs = getSharedPreferences(LauncherAppState.getSharedPreferencesKey(),
431 Context.MODE_PRIVATE);
432 mIsSafeModeEnabled = getPackageManager().isSafeMode();
433 mModel = app.setLauncher(this);
434 mIconCache = app.getIconCache();
435 mIconCache.flushInvalidIcons(grid);
436 mDragController = new DragController(this);
437 mInflater = getLayoutInflater();
438 mStateTransitionAnimation = new LauncherStateTransitionAnimation(this, this);
439
440 mStats = new Stats(this);
441
442 mAppWidgetManager = AppWidgetManagerCompat.getInstance(this);
443
444 mAppWidgetHost = new LauncherAppWidgetHost(this, APPWIDGET_HOST_ID);
445 mAppWidgetHost.startListening();
446
447 // If we are getting an onCreate, we can actually preempt onResume and unset mPaused here,
448 // this also ensures that any synchronous binding below doesn't re-trigger another
449 // LauncherModel load.
450 mPaused = false;
451
452 if (PROFILE_STARTUP) {
453 android.os.Debug.startMethodTracing(
454 Environment.getExternalStorageDirectory() + "/launcher");
455 }
456
457 checkForLocaleChange();
458 setContentView(R.layout.launcher);
459
460 setupViews();
461 grid.layout(this);
462
463 registerContentObservers();
464
465 lockAllApps();
466
467 mSavedState = savedInstanceState;
468 restoreState(mSavedState);
469
470 if (PROFILE_STARTUP) {
471 android.os.Debug.stopMethodTracing();
472 }
473
474 if (!mRestoring) {
475 if (DISABLE_SYNCHRONOUS_BINDING_CURRENT_PAGE) {
476 // If the user leaves launcher, then we should just load items asynchronously when
477 // they return.
478 mModel.startLoader(true, PagedView.INVALID_RESTORE_PAGE);
479 } else {
480 // We only load the page synchronously if the user rotates (or triggers a
481 // configuration change) while launcher is in the foreground
482 mModel.startLoader(true, mWorkspace.getRestorePage());
483 }
484 }
485
486 // For handling default keys
487 mDefaultKeySsb = new SpannableStringBuilder();
488 Selection.setSelection(mDefaultKeySsb, 0);
489
490 IntentFilter filter = new IntentFilter(Intent.ACTION_CLOSE_SYSTEM_DIALOGS);
491 registerReceiver(mCloseSystemDialogsReceiver, filter);
492
493 // On large interfaces, we want the screen to auto-rotate based on the current orientation
494 unlockScreenOrientation(true);
495
496 if (mLauncherCallbacks != null) {
497 mLauncherCallbacks.onCreate(savedInstanceState);
498 if (mLauncherCallbacks.hasLauncherOverlay()) {
499 ViewStub stub = (ViewStub) findViewById(R.id.launcher_overlay_stub);
500 mLauncherOverlayContainer = (InsettableFrameLayout) stub.inflate();
501 mLauncherOverlay = mLauncherCallbacks.setLauncherOverlayView(
502 mLauncherOverlayContainer, mLauncherOverlayCallbacks);
503 mWorkspace.setLauncherOverlay(mLauncherOverlay);
504 }
505 }
506
507 if (shouldShowIntroScreen()) {
508 showIntroScreen();
509 } else {
510 showFirstRunActivity();
511 showFirstRunClings();
512 }
513 }
514
515 private LauncherCallbacks mLauncherCallbacks;
516
517 public void onPostCreate(Bundle savedInstanceState) {
518 super.onPostCreate(savedInstanceState);
519 if (mLauncherCallbacks != null) {
520 mLauncherCallbacks.onPostCreate(savedInstanceState);
521 }
522 }
523
524 public boolean setLauncherCallbacks(LauncherCallbacks callbacks) {
525 mLauncherCallbacks = callbacks;
526 mLauncherCallbacks.setLauncherAppsCallback(new Launcher.LauncherAppsCallbacks() {
527 @Override
528 public void onAllAppsBoundsChanged(Rect bounds) {
529 mAppsView.setFixedBounds(Launcher.this, bounds);
530 }
531
532 @Override
533 public void dismissAllApps() {
534 showWorkspace(true);
535 }
536 });
537 return true;
538 }
539
540 @Override
541 public void onLauncherProviderChange() {
542 if (mLauncherCallbacks != null) {
543 mLauncherCallbacks.onLauncherProviderChange();
544 }
545 }
546
547 /** To be overridden by subclasses to hint to Launcher that we have custom content */
548 protected boolean hasCustomContentToLeft() {
549 if (mLauncherCallbacks != null) {
550 return mLauncherCallbacks.hasCustomContentToLeft();
551 }
552 return false;
553 }
554
555 /**
556 * To be overridden by subclasses to populate the custom content container and call
557 * {@link #addToCustomContentPage}. This will only be invoked if
558 * {@link #hasCustomContentToLeft()} is {@code true}.
559 */
560 protected void populateCustomContentContainer() {
561 if (mLauncherCallbacks != null) {
562 mLauncherCallbacks.populateCustomContentContainer();
563 }
564 }
565
566 /**
567 * Invoked by subclasses to signal a change to the {@link #addCustomContentToLeft} value to
568 * ensure the custom content page is added or removed if necessary.
569 */
570 protected void invalidateHasCustomContentToLeft() {
571 if (mWorkspace == null || mWorkspace.getScreenOrder().isEmpty()) {
572 // Not bound yet, wait for bindScreens to be called.
573 return;
574 }
575
576 if (!mWorkspace.hasCustomContent() && hasCustomContentToLeft()) {
577 // Create the custom content page and call the subclass to populate it.
578 mWorkspace.createCustomContentContainer();
579 populateCustomContentContainer();
580 } else if (mWorkspace.hasCustomContent() && !hasCustomContentToLeft()) {
581 mWorkspace.removeCustomContentPage();
582 }
583 }
584
585 @Thunk void checkForLocaleChange() {
586 if (sLocaleConfiguration == null) {
587 new AsyncTask<Void, Void, LocaleConfiguration>() {
588 @Override
589 protected LocaleConfiguration doInBackground(Void... unused) {
590 LocaleConfiguration localeConfiguration = new LocaleConfiguration();
591 readConfiguration(Launcher.this, localeConfiguration);
592 return localeConfiguration;
593 }
594
595 @Override
596 protected void onPostExecute(LocaleConfiguration result) {
597 sLocaleConfiguration = result;
598 checkForLocaleChange(); // recursive, but now with a locale configuration
599 }
600 }.execute();
601 return;
602 }
603
604 final Configuration configuration = getResources().getConfiguration();
605
606 final String previousLocale = sLocaleConfiguration.locale;
607 final String locale = configuration.locale.toString();
608
609 final int previousMcc = sLocaleConfiguration.mcc;
610 final int mcc = configuration.mcc;
611
612 final int previousMnc = sLocaleConfiguration.mnc;
613 final int mnc = configuration.mnc;
614
615 boolean localeChanged = !locale.equals(previousLocale) || mcc != previousMcc || mnc != previousMn🔵
616
617 if (localeChanged) {
618 sLocaleConfiguration.locale = locale;
619 sLocaleConfiguration.mcc = mcc;
620 sLocaleConfiguration.mnc = mnc;
621
622 mIconCache.flush();
623
624 final LocaleConfiguration localeConfiguration = sLocaleConfiguration;
625 new AsyncTask<Void, Void, Void>() {
626 public Void doInBackground(Void ... args) {
627 writeConfiguration(Launcher.this, localeConfiguration);
628 return null;
629 }
630 }.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR, (Void) null);
631 }
632 }
633
634 @Thunk static class LocaleConfiguration {
635 public String locale;
636 public int mcc = -1;
637 public int mnc = -1;
638 }
639
640 @Thunk static void readConfiguration(Context context, LocaleConfiguration configuration) {
641 DataInputStream in = null;
642 try {
643 in = new DataInputStream(context.openFileInput(LauncherFiles.LAUNCHER_PREFERENCES));
644 configuration.locale = in.readUTF();
645 configuration.mcc = in.readInt();
646 configuration.mnc = in.readInt();
647 } catch (FileNotFoundException e) {
648 // Ignore
649 } catch (IOException e) {
650 // Ignore
651 } finally {
652 if (in != null) {
653 try {
654 in.close();
655 } catch (IOException e) {
656 // Ignore
657 }
658 }
659 }
660 }
661
662 @Thunk static void writeConfiguration(Context context, LocaleConfiguration configuration) {
663 DataOutputStream out = null;
664 try {
665 out = new DataOutputStream(context.openFileOutput(
666 LauncherFiles.LAUNCHER_PREFERENCES, MODE_PRIVATE));
667 out.writeUTF(configuration.locale);
668 out.writeInt(configuration.mcc);
669 out.writeInt(configuration.mnc);
670 out.flush();
671 } catch (FileNotFoundException e) {
672 // Ignore
673 } catch (IOException e) {
674 //noinspection ResultOfMethodCallIgnored
675 context.getFileStreamPath(LauncherFiles.LAUNCHER_PREFERENCES).delete();
676 } finally {
677 if (out != null) {
678 try {
679 out.close();
680 } catch (IOException e) {
681 // Ignore
682 }
683 }
684 }
685 }
686
687 public Stats getStats() {
688 return mStats;
689 }
690
691 public LayoutInflater getInflater() {
692 return mInflater;
693 }
694
695 public boolean isDraggingEnabled() {
696 // We prevent dragging when we are loading the workspace as it is possible to pick up a view
697 // that is subsequently removed from the workspace in startBinding().
698 return !mModel.isLoadingWorkspace();
699 }
700
701 @TargetApi(Build.VERSION_CODES.JELLY_BEAN_MR1)
702 public static int generateViewId() {
703 if (Build.VERSION.SDK_INT >= 17) {
704 return View.generateViewId();
705 } else {
706 // View.generateViewId() is not available. The following fallback logic is a copy
707 // of its implementation.
708 for (;;) {
709 final int result = sNextGeneratedId.get();
710 // aapt-generated IDs have the high byte nonzero; clamp to the range under that.
711 int newValue = result + 1;
712 if (newValue > 0x00FFFFFF) newValue = 1; // Roll over to 1, not 0.
713 if (sNextGeneratedId.compareAndSet(result, newValue)) {
714 return result;
715 }
716 }
717 }
718 }
719
720 public int getViewIdForItem(ItemInfo info) {
721 // This cast is safe given the > 2B range for int.
722 int itemId = (int) info.id;
723 if (mItemIdToViewId.containsKey(itemId)) {
724 return mItemIdToViewId.get(itemId);
725 }
726 int viewId = generateViewId();
727 mItemIdToViewId.put(itemId, viewId);
728 return viewId;
729 }
730
731 /**
732 * Returns whether we should delay spring loaded mode -- for shortcuts and widgets that have
733 * a configuration step, this allows the proper animations to run after other transitions.
734 */
735 private long completeAdd(PendingAddArguments args) {
736 long screenId = args.screenId;
737 if (args.container == LauncherSettings.Favorites.CONTAINER_DESKTOP) {
738 // When the screen id represents an actual screen (as opposed to a rank) we make sure
739 // that the drop page actually exists.
740 screenId = ensurePendingDropLayoutExists(args.screenId);
741 }
742
743 switch (args.requestCode) {
744 case REQUEST_CREATE_SHORTCUT:
745 completeAddShortcut(args.intent, args.container, screenId, args.cellX,
746 args.cellY);
747 break;
748 case REQUEST_CREATE_APPWIDGET:
749 completeAddAppWidget(args.appWidgetId, args.container, screenId, null, null);
750 break;
751 case REQUEST_RECONFIGURE_APPWIDGET:
752 completeRestoreAppWidget(args.appWidgetId);
753 break;
754 }
755 // Before adding this resetAddInfo(), after a shortcut was added to a workspace screen,
756 // if you turned the screen off and then back while in All Apps, Launcher would not
757 // return to the workspace. Clearing mAddInfo.container here fixes this issue
758 resetAddInfo();
759 return screenId;
760 }
761
762 private void handleActivityResult(
763 final int requestCode, final int resultCode, final Intent data) {
764 // Reset the startActivity waiting flag
765 setWaitingForResult(false);
766 final int pendingAddWidgetId = mPendingAddWidgetId;
767 mPendingAddWidgetId = -1;
768
769 Runnable exitSpringLoaded = new Runnable() {
770 @Override
771 public void run() {
772 exitSpringLoadedDragModeDelayed((resultCode != RESULT_CANCELED),
773 EXIT_SPRINGLOADED_MODE_SHORT_TIMEOUT, null);
774 }
775 };
776
777 if (requestCode == REQUEST_BIND_APPWIDGET) {
778 final int appWidgetId = data != null ?
779 data.getIntExtra(AppWidgetManager.EXTRA_APPWIDGET_ID, -1) : -1;
780 if (resultCode == RESULT_CANCELED) {
781 completeTwoStageWidgetDrop(RESULT_CANCELED, appWidgetId);
782 mWorkspace.removeExtraEmptyScreenDelayed(true, exitSpringLoaded,
783 ON_ACTIVITY_RESULT_ANIMATION_DELAY, false);
784 } else if (resultCode == RESULT_OK) {
785 addAppWidgetImpl(appWidgetId, mPendingAddInfo, null,
786 mPendingAddWidgetInfo, ON_ACTIVITY_RESULT_ANIMATION_DELAY);
787 }
788 return;
789 } else if (requestCode == REQUEST_PICK_WALLPAPER) {
790 if (resultCode == RESULT_OK && mWorkspace.isInOverviewMode()) {
791 mWorkspace.exitOverviewMode(false);
792 }
793 return;
794 }
795
796 boolean isWidgetDrop = (requestCode == REQUEST_PICK_APPWIDGET ||
797 requestCode == REQUEST_CREATE_APPWIDGET);
798
799 final boolean workspaceLocked = isWorkspaceLocked();
800 // We have special handling for widgets
801 if (isWidgetDrop) {
802 final int appWidgetId;
803 int widgetId = data != null ? data.getIntExtra(AppWidgetManager.EXTRA_APPWIDGET_ID, -1)
804 : -1;
805 if (widgetId < 0) {
806 appWidgetId = pendingAddWidgetId;
807 } else {
808 appWidgetId = widgetId;
809 }
810
811 final int result;
812 if (appWidgetId < 0 || resultCode == RESULT_CANCELED) {
813 Log.e(TAG, "Error: appWidgetId (EXTRA_APPWIDGET_ID) was not " +
814 "returned from the widget configuration activity.");
815 result = RESULT_CANCELED;
816 completeTwoStageWidgetDrop(result, appWidgetId);
817 final Runnable onComplete = new Runnable() {
818 @Override
819 public void run() {
820 exitSpringLoadedDragModeDelayed(false, 0, null);
821 }
822 };
823 if (workspaceLocked) {
824 // No need to remove the empty screen if we're mid-binding, as the
825 // the bind will not add the empty screen.
826 mWorkspace.postDelayed(onComplete, ON_ACTIVITY_RESULT_ANIMATION_DELAY);
827 } else {
828 mWorkspace.removeExtraEmptyScreenDelayed(true, onComplete,
829 ON_ACTIVITY_RESULT_ANIMATION_DELAY, false);
830 }
831 } else {
832 if (!workspaceLocked) {
833 if (mPendingAddInfo.container == LauncherSettings.Favorites.CONTAINER_DESKTOP) {
834 // When the screen id represents an actual screen (as opposed to a rank)
835 // we make sure that the drop page actually exists.
836 mPendingAddInfo.screenId =
837 ensurePendingDropLayoutExists(mPendingAddInfo.screenId);
838 }
839 final CellLayout dropLayout = mWorkspace.getScreenWithId(mPendingAddInfo.screenId);
840
841 dropLayout.setDropPending(true);
842 final Runnable onComplete = new Runnable() {
843 @Override
844 public void run() {
845 completeTwoStageWidgetDrop(resultCode, appWidgetId);
846 dropLayout.setDropPending(false);
847 }
848 };
849 mWorkspace.removeExtraEmptyScreenDelayed(true, onComplete,
850 ON_ACTIVITY_RESULT_ANIMATION_DELAY, false);
851 } else {
852 PendingAddArguments args = preparePendingAddArgs(requestCode, data, appWidgetId,
853 mPendingAddInfo);
854 sPendingAddItem = args;
855 }
856 }
857 return;
858 }
859
860 if (requestCode == REQUEST_RECONFIGURE_APPWIDGET) {
861 if (resultCode == RESULT_OK) {
862 // Update the widget view.
863 PendingAddArguments args = preparePendingAddArgs(requestCode, data,
864 pendingAddWidgetId, mPendingAddInfo);
865 if (workspaceLocked) {
866 sPendingAddItem = args;
867 } else {
868 completeAdd(args);
869 }
870 }
871 // Leave the widget in the pending state if the user canceled the configure.
872 return;
873 }
874
875 // The pattern used here is that a user PICKs a specific application,
876 // which, depending on the target, might need to CREATE the actual target.
877
878 // For example, the user would PICK_SHORTCUT for "Music playlist", and we
879 // launch over to the Music app to actually CREATE_SHORTCUT.
880 if (resultCode == RESULT_OK && mPendingAddInfo.container != ItemInfo.NO_ID) {
881 final PendingAddArguments args = preparePendingAddArgs(requestCode, data, -1,
882 mPendingAddInfo);
883 if (isWorkspaceLocked()) {
884 sPendingAddItem = args;
885 } else {
886 completeAdd(args);
887 mWorkspace.removeExtraEmptyScreenDelayed(true, exitSpringLoaded,
888 ON_ACTIVITY_RESULT_ANIMATION_DELAY, false);
889 }
890 } else if (resultCode == RESULT_CANCELED) {
891 mWorkspace.removeExtraEmptyScreenDelayed(true, exitSpringLoaded,
892 ON_ACTIVITY_RESULT_ANIMATION_DELAY, false);
893 }
894 mDragLayer.clearAnimatedView();
895
896 }
897
898 @Override
899 protected void onActivityResult(
900 final int requestCode, final int resultCode, final Intent data) {
901 handleActivityResult(requestCode, resultCode, data);
902 if (mLauncherCallbacks != null) {
903 mLauncherCallbacks.onActivityResult(requestCode, resultCode, data);
904 }
905 }
906
907 private PendingAddArguments preparePendingAddArgs(int requestCode, Intent data, int
908 appWidgetId, ItemInfo info) {
909 PendingAddArguments args = new PendingAddArguments();
910 args.requestCode = requestCode;
911 args.intent = data;
912 args.container = info.container;
913 args.screenId = info.screenId;
914 args.cellX = info.cellX;
915 args.cellY = info.cellY;
916 args.appWidgetId = appWidgetId;
917 return args;
918 }
919
920 /**
921 * Check to see if a given screen id exists. If not, create it at the end, return the new id.
922 *
923 * @param screenId the screen id to check
924 * @return the new screen, or screenId if it exists
925 */
926 private long ensurePendingDropLayoutExists(long screenId) {
927 CellLayout dropLayout =
928 (CellLayout) mWorkspace.getScreenWithId(screenId);
929 if (dropLayout == null) {
930 // it's possible that the add screen was removed because it was
931 // empty and a re-bind occurred
932 mWorkspace.addExtraEmptyScreen();
933 return mWorkspace.commitExtraEmptyScreen();
934 } else {
935 return screenId;
936 }
937 }
938
939 @Thunk void completeTwoStageWidgetDrop(final int resultCode, final int appWidgetId) {
940 CellLayout cellLayout =
941 (CellLayout) mWorkspace.getScreenWithId(mPendingAddInfo.screenId);
942 Runnable onCompleteRunnable = null;
943 int animationType = 0;
944
945 AppWidgetHostView boundWidget = null;
946 if (resultCode == RESULT_OK) {
947 animationType = Workspace.COMPLETE_TWO_STAGE_WIDGET_DROP_ANIMATION;
948 final AppWidgetHostView layout = mAppWidgetHost.createView(this, appWidgetId,
949 mPendingAddWidgetInfo);
950 boundWidget = layout;
951 onCompleteRunnable = new Runnable() {
952 @Override
953 public void run() {
954 completeAddAppWidget(appWidgetId, mPendingAddInfo.container,
955 mPendingAddInfo.screenId, layout, null);
956 exitSpringLoadedDragModeDelayed((resultCode != RESULT_CANCELED),
957 EXIT_SPRINGLOADED_MODE_SHORT_TIMEOUT, null);
958 }
959 };
960 } else if (resultCode == RESULT_CANCELED) {
961 mAppWidgetHost.deleteAppWidgetId(appWidgetId);
962 animationType = Workspace.CANCEL_TWO_STAGE_WIDGET_DROP_ANIMATION;
963 }
964 if (mDragLayer.getAnimatedView() != null) {
965 mWorkspace.animateWidgetDrop(mPendingAddInfo, cellLayout,
966 (DragView) mDragLayer.getAnimatedView(), onCompleteRunnable,
967 animationType, boundWidget, true);
968 } else if (onCompleteRunnable != null) {
969 // The animated view may be null in the case of a rotation during widget configuration
970 onCompleteRunnable.run();
971 }
972 }
973
974 @Override
975 protected void onStop() {
976 super.onStop();
977 FirstFrameAnimatorHelper.setIsVisible(false);
978
979 if (mLauncherCallbacks != null) {
980 mLauncherCallbacks.onStop();
981 }
982 }
983
984 @Override
985 protected void onStart() {
986 super.onStart();
987 FirstFrameAnimatorHelper.setIsVisible(true);
988
989 if (mLauncherCallbacks != null) {
990 mLauncherCallbacks.onStart();
991 }
992 }
993
994 @Override
995 protected void onResume() {
996 long startTime = 0;
997 if (DEBUG_RESUME_TIME) {
998 startTime = System.currentTimeMillis();
999 Log.v(TAG, "Launcher.onResume()");
1000 }
1001
1002 if (mLauncherCallbacks != null) {
1003 mLauncherCallbacks.preOnResume();
1004 }
1005
1006 super.onResume();
1007
1008 // Restore the previous launcher state
1009 if (mOnResumeState == State.WORKSPACE) {
1010 showWorkspace(false);
1011 } else if (mOnResumeState == State.APPS) {
1012 showAppsView(false /* animated */, false /* resetListToTop */);
1013 } else if (mOnResumeState == State.WIDGETS) {
1014 showWidgetsView(false, false);
1015 }
1016 mOnResumeState = State.NONE;
1017
1018 // Background was set to gradient in onPause(), restore to black if in all apps.
1019 setWorkspaceBackground(mState == State.WORKSPACE);
1020
1021 mPaused = false;
1022 if (mRestoring || mOnResumeNeedsLoad) {
1023 setWorkspaceLoading(true);
1024 mModel.startLoader(true, PagedView.INVALID_RESTORE_PAGE);
1025 mRestoring = false;
1026 mOnResumeNeedsLoad = false;
1027 }
1028 if (mBindOnResumeCallbacks.size() > 0) {
1029 // We might have postponed some bind calls until onResume (see waitUntilResume) --
1030 // execute them here
1031 long startTimeCallbacks = 0;
1032 if (DEBUG_RESUME_TIME) {
1033 startTimeCallbacks = System.currentTimeMillis();
1034 }
1035
1036 for (int i = 0; i < mBindOnResumeCallbacks.size(); i++) {
1037 mBindOnResumeCallbacks.get(i).run();
1038 }
1039 mBindOnResumeCallbacks.clear();
1040 if (DEBUG_RESUME_TIME) {
1041 Log.d(TAG, "Time spent processing callbacks in onResume: " +
1042 (System.currentTimeMillis() - startTimeCallbacks));
1043 }
1044 }
1045 if (mOnResumeCallbacks.size() > 0) {
1046 for (int i = 0; i < mOnResumeCallbacks.size(); i++) {
1047 mOnResumeCallbacks.get(i).run();
1048 }
1049 mOnResumeCallbacks.clear();
1050 }
1051
1052 // Reset the pressed state of icons that were locked in the press state while activities
1053 // were launching
1054 if (mWaitingForResume != null) {
1055 // Resets the previous workspace icon press state
1056 mWaitingForResume.setStayPressed(false);
1057 }
1058
1059 // It is possible that widgets can receive updates while launcher is not in the foreground.
1060 // Consequently, the widgets will be inflated in the orientation of the foreground activity
1061 // (framework issue). On resuming, we ensure that any widgets are inflated for the current
1062 // orientation.
1063 getWorkspace().reinflateWidgetsIfNecessary();
1064 reinflateQSBIfNecessary();
1065
1066 // Process any items that were added while Launcher was away.
1067 InstallShortcutReceiver.disableAndFlushInstallQueue(this);
1068
1069 if (DEBUG_RESUME_TIME) {
1070 Log.d(TAG, "Time spent in onResume: " + (System.currentTimeMillis() - startTime));
1071 }
1072
1073 if (mWorkspace.getCustomContentCallbacks() != null) {
1074 // If we are resuming and the custom content is the current page, we call onShow().
1075 // It is also poassible that onShow will instead be called slightly after first layout
1076 // if PagedView#setRestorePage was set to the custom content page in onCreate().
1077 if (mWorkspace.isOnOrMovingToCustomContent()) {
1078 mWorkspace.getCustomContentCallbacks().onShow(true);
1079 }
1080 }
1081 mWorkspace.updateInteractionForState();
1082 mWorkspace.onResume();
1083
1084 PackageInstallerCompat.getInstance(this).onResume();
1085
1086 if (mLauncherCallbacks != null) {
1087 mLauncherCallbacks.onResume();
1088 }
1089 }
1090
1091 @Override
1092 protected void onPause() {
1093 // Ensure that items added to Launcher are queued until Launcher returns
1094 InstallShortcutReceiver.enableInstallQueue();
1095 PackageInstallerCompat.getInstance(this).onPause();
1096
1097 super.onPause();
1098 mPaused = true;
1099 mDragController.cancelDrag();
1100 mDragController.resetLastGestureUpTime();
1101
1102 // We call onHide() aggressively. The custom content callbacks should be able to
1103 // debounce excess onHide calls.
1104 if (mWorkspace.getCustomContentCallbacks() != null) {
1105 mWorkspace.getCustomContentCallbacks().onHide();
1106 }
1107
1108 if (mLauncherCallbacks != null) {
1109 mLauncherCallbacks.onPause();
1110 }
1111 }
1112
1113 public interface CustomContentCallbacks {
1114 // Custom content is completely shown. {@code fromResume} indicates whether this was caused
1115 // by a onResume or by scrolling otherwise.
1116 public void onShow(boolean fromResume);
1117
1118 // Custom content is completely hidden
1119 public void onHide();
1120
1121 // Custom content scroll progress changed. From 0 (not showing) to 1 (fully showing).
1122 public void onScrollProgressChanged(float progress);
1123
1124 // Indicates whether the user is allowed to scroll away from the custom content.
1125 boolean isScrollingAllowed();
1126 }
1127
1128 public interface LauncherOverlay {
1129
1130 /**
1131 * Touch interaction leading to overscroll has begun
1132 */
1133 public void onScrollInteractionBegin();
1134
1135 /**
1136 * Touch interaction related to overscroll has ended
1137 */
1138 public void onScrollInteractionEnd();
1139
1140 /**
1141 * Scroll progress, between 0 and 100, when the user scrolls beyond the leftmost
1142 * screen (or in the case of RTL, the rightmost screen).
1143 */
1144 public void onScrollChange(int progress, boolean rtl);
1145
1146 /**
1147 * Screen has stopped scrolling
1148 */
1149 public void onScrollSettled();
1150
1151 /**
1152 * This method can be called by the Launcher in order to force the LauncherOverlay
1153 * to exit fully immersive mode.
1154 */
1155 public void forceExitFullImmersion();
1156 }
1157
1158 public interface LauncherAppsCallbacks {
1159 /**
1160 * Updates launcher to the available space that AllApps can take so as not to overlap with
1161 * any other views.
1162 */
1163 public void onAllAppsBoundsChanged(Rect bounds);
1164
1165 /**
1166 * Called to dismiss all apps if it is showing.
1167 */
1168 public void dismissAllApps();
1169 }
1170
1171 public interface LauncherOverlayCallbacks {
1172 /**
1173 * This method indicates whether a call to {@link #enterFullImmersion()} will succeed,
1174 * however it doesn't modify any state within the launcher.
1175 */
1176 public boolean canEnterFullImmersion();
1177
1178 /**
1179 * Should be called to tell Launcher that the LauncherOverlay will take over interaction,
1180 * eg. by occupying the full screen and handling all touch events.
1181 *
1182 * @return true if Launcher allows the LauncherOverlay to become fully immersive. In this
1183 * case, Launcher will modify any necessary state and assumes the overlay is
1184 * handling all interaction. If false, the LauncherOverlay should cancel any
1185 *
1186 */
1187 public boolean enterFullImmersion();
1188
1189 /**
1190 * Must be called when exiting fully immersive mode. Indicates to Launcher that it has
1191 * full control over UI and state.
1192 */
1193 public void exitFullImmersion();
1194 }
1195
1196 class LauncherOverlayCallbacksImpl implements LauncherOverlayCallbacks {
1197
1198 @Override
1199 public boolean canEnterFullImmersion() {
1200 return mState == State.WORKSPACE;
1201 }
1202
1203 @Override
1204 public boolean enterFullImmersion() {
1205 if (mState == State.WORKSPACE) {
1206 // When fully immersed, disregard any touches which fall through.
1207 mDragLayer.setBlockTouch(true);
1208 return true;
1209 }
1210 return false;
1211 }
1212
1213 @Override
1214 public void exitFullImmersion() {
1215 mDragLayer.setBlockTouch(false);
1216 }
1217 }
1218
1219 protected boolean hasSettings() {
1220 if (mLauncherCallbacks != null) {
1221 return mLauncherCallbacks.hasSettings();
1222 }
1223 return false;
1224 }
1225
1226
1227 public void addToCustomContentPage(View customContent,
1228 CustomContentCallbacks callbacks, String description) {
1229 mWorkspace.addToCustomContentPage(customContent, callbacks, description);
1230 }
1231
1232 // The custom content needs to offset its content to account for the QSB
1233 public int getTopOffsetForCustomContent() {
1234 return mWorkspace.getPaddingTop();
1235 }
1236
1237 @Override
1238 public Object onRetainNonConfigurationInstance() {
1239 // Flag the loader to stop early before switching
1240 if (mModel.isCurrentCallbacks(this)) {
1241 mModel.stopLoader();
1242 }
1243 //TODO(hyunyoungs): stop the widgets loader when there is a rotation.
1244
1245 return Boolean.TRUE;
1246 }
1247
1248 // We can't hide the IME if it was forced open. So don't bother
1249 @Override
1250 public void onWindowFocusChanged(boolean hasFocus) {
1251 super.onWindowFocusChanged(hasFocus);
1252 mHasFocus = hasFocus;
1253
1254 if (mLauncherCallbacks != null) {
1255 mLauncherCallbacks.onWindowFocusChanged(hasFocus);
1256 }
1257 }
1258
1259 private boolean acceptFilter() {
1260 final InputMethodManager inputManager = (InputMethodManager)
1261 getSystemService(Context.INPUT_METHOD_SERVICE);
1262 return !inputManager.isFullscreenMode();
1263 }
1264
1265 @Override
1266 public boolean onKeyDown(int keyCode, KeyEvent event) {
1267 final int uniChar = event.getUnicodeChar();
1268 final boolean handled = super.onKeyDown(keyCode, event);
1269 final boolean isKeyNotWhitespace = uniChar > 0 && !Character.isWhitespace(uniChar);
1270 if (!handled && acceptFilter() && isKeyNotWhitespace) {
1271 boolean gotKey = TextKeyListener.getInstance().onKeyDown(mWorkspace, mDefaultKeySsb,
1272 keyCode, event);
1273 if (gotKey && mDefaultKeySsb != null && mDefaultKeySsb.length() > 0) {
1274 // something usable has been typed - start a search
1275 // the typed text will be retrieved and cleared by
1276 // showSearchDialog()
1277 // If there are multiple keystrokes before the search dialog takes focus,
1278 // onSearchRequested() will be called for every keystroke,
1279 // but it is idempotent, so it's fine.
1280 return onSearchRequested();
1281 }
1282 }
1283
1284 // Eat the long press event so the keyboard doesn't come up.
1285 if (keyCode == KeyEvent.KEYCODE_MENU && event.isLongPress()) {
1286 return true;
1287 }
1288
1289 return handled;
1290 }
1291
1292 private String getTypedText() {
1293 return mDefaultKeySsb.toString();
1294 }
1295
1296 private void clearTypedText() {
1297 mDefaultKeySsb.clear();
1298 mDefaultKeySsb.clearSpans();
1299 Selection.setSelection(mDefaultKeySsb, 0);
1300 }
1301
1302 /**
1303 * Given the integer (ordinal) value of a State enum instance, convert it to a variable of type
1304 * State
1305 */
1306 private static State intToState(int stateOrdinal) {
1307 State state = State.WORKSPACE;
1308 final State[] stateValues = State.values();
1309 for (int i = 0; i < stateValues.length; i++) {
1310 if (stateValues[i].ordinal() == stateOrdinal) {
1311 state = stateValues[i];
1312 break;
1313 }
1314 }
1315 return state;
1316 }
1317
1318 /**
1319 * Restores the previous state, if it exists.
1320 *
1321 * @param savedState The previous state.
1322 */
1323 @SuppressWarnings("unchecked")
1324 private void restoreState(Bundle savedState) {
1325 if (savedState == null) {
1326 return;
1327 }
1328
1329 State state = intToState(savedState.getInt(RUNTIME_STATE, State.WORKSPACE.ordinal()));
1330 if (state == State.APPS || state == State.WIDGETS) {
1331 mOnResumeState = state;
1332 }
1333
1334 int currentScreen = savedState.getInt(RUNTIME_STATE_CURRENT_SCREEN,
1335 PagedView.INVALID_RESTORE_PAGE);
1336 if (currentScreen != PagedView.INVALID_RESTORE_PAGE) {
1337 mWorkspace.setRestorePage(currentScreen);
1338 }
1339
1340 final long pendingAddContainer = savedState.getLong(RUNTIME_STATE_PENDING_ADD_CONTAINER, -1);
1341 final long pendingAddScreen = savedState.getLong(RUNTIME_STATE_PENDING_ADD_SCREEN, -1);
1342
1343 if (pendingAddContainer != ItemInfo.NO_ID && pendingAddScreen > -1) {
1344 mPendingAddInfo.container = pendingAddContainer;
1345 mPendingAddInfo.screenId = pendingAddScreen;
1346 mPendingAddInfo.cellX = savedState.getInt(RUNTIME_STATE_PENDING_ADD_CELL_X);
1347 mPendingAddInfo.cellY = savedState.getInt(RUNTIME_STATE_PENDING_ADD_CELL_Y);
1348 mPendingAddInfo.spanX = savedState.getInt(RUNTIME_STATE_PENDING_ADD_SPAN_X);
1349 mPendingAddInfo.spanY = savedState.getInt(RUNTIME_STATE_PENDING_ADD_SPAN_Y);
1350 AppWidgetProviderInfo info = savedState.getParcelable(
1351 RUNTIME_STATE_PENDING_ADD_WIDGET_INFO);
1352 mPendingAddWidgetInfo = LauncherAppWidgetProviderInfo.fromProviderInfo(this, info);
1353 mPendingAddWidgetId = savedState.getInt(RUNTIME_STATE_PENDING_ADD_WIDGET_ID);
1354 setWaitingForResult(true);
1355 mRestoring = true;
1356 }
1357
1358 boolean renameFolder = savedState.getBoolean(RUNTIME_STATE_PENDING_FOLDER_RENAME, false);
1359 if (renameFolder) {
1360 long id = savedState.getLong(RUNTIME_STATE_PENDING_FOLDER_RENAME_ID);
1361 mFolderInfo = mModel.getFolderById(this, sFolders, id);
1362 mRestoring = true;
1363 }
1364
1365 mItemIdToViewId = (HashMap<Integer, Integer>)
1366 savedState.getSerializable(RUNTIME_STATE_VIEW_IDS);
1367 }
1368
1369 /**
1370 * Finds all the views we need and configure them properly.
1371 */
1372 private void setupViews() {
1373 final DragController dragController = mDragController;
1374
1375 mLauncherView = findViewById(R.id.launcher);
1376 mFocusHandler = (FocusIndicatorView) findViewById(R.id.focus_indicator);
1377 mDragLayer = (DragLayer) findViewById(R.id.drag_layer);
1378 mWorkspace = (Workspace) mDragLayer.findViewById(R.id.workspace);
1379 mWorkspace.setPageSwitchListener(this);
1380 mPageIndicators = mDragLayer.findViewById(R.id.page_indicator);
1381
1382 mLauncherView.setSystemUiVisibility(
1383 View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN | View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION);
1384 mWorkspaceBackgroundDrawable = getResources().getDrawable(R.drawable.workspace_bg);
1385
1386 // Setup the drag layer
1387 mDragLayer.setup(this, dragController);
1388
1389 // Setup the hotseat
1390 mHotseat = (Hotseat) findViewById(R.id.hotseat);
1391 if (mHotseat != null) {
1392 mHotseat.setup(this);
1393 mHotseat.setOnLongClickListener(this);
1394 }
1395
1396 mOverviewPanel = (ViewGroup) findViewById(R.id.overview_panel);
1397 View widgetButton = findViewById(R.id.widget_button);
1398 widgetButton.setOnClickListener(new OnClickListener() {
1399 @Override
1400 public void onClick(View arg0) {
1401 if (!mWorkspace.isSwitchingState()) {
1402 onClickAddWidgetButton(arg0);
1403 }
1404 }
1405 });
1406 widgetButton.setOnTouchListener(getHapticFeedbackTouchListener());
1407
1408 View wallpaperButton = findViewById(R.id.wallpaper_button);
1409 wallpaperButton.setOnClickListener(new OnClickListener() {
1410 @Override
1411 public void onClick(View arg0) {
1412 if (!mWorkspace.isSwitchingState()) {
1413 onClickWallpaperPicker(arg0);
1414 }
1415 }
1416 });
1417 wallpaperButton.setOnTouchListener(getHapticFeedbackTouchListener());
1418
1419 View settingsButton = findViewById(R.id.settings_button);
1420 if (hasSettings()) {
1421 settingsButton.setOnClickListener(new OnClickListener() {
1422 @Override
1423 public void onClick(View arg0) {
1424 if (!mWorkspace.isSwitchingState()) {
1425 onClickSettingsButton(arg0);
1426 }
1427 }
1428 });
1429 settingsButton.setOnTouchListener(getHapticFeedbackTouchListener());
1430 } else {
1431 settingsButton.setVisibility(View.GONE);
1432 }
1433
1434 mOverviewPanel.setAlpha(0f);
1435
1436 // Setup the workspace
1437 mWorkspace.setHapticFeedbackEnabled(false);
1438 mWorkspace.setOnLongClickListener(this);
1439 mWorkspace.setup(dragController);
1440 dragController.addDragListener(mWorkspace);
1441
1442 // Get the search/delete bar
1443 mSearchDropTargetBar = (SearchDropTargetBar)
1444 mDragLayer.findViewById(R.id.search_drop_target_bar);
1445
1446 // Setup Apps
1447 mAppsView = (AppsContainerView) findViewById(R.id.apps_view);
1448 if (mLauncherCallbacks != null && mLauncherCallbacks.overrideAllAppsSearch()) {
1449 mAppsView.hideSearchBar();
1450 }
1451
1452 // Setup AppsCustomize
1453 mWidgetsView = (WidgetsContainerView) findViewById(R.id.widgets_view);
1454
1455 // Setup the drag controller (drop targets have to be added in reverse order in priority)
1456 dragController.setDragScoller(mWorkspace);
1457 dragController.setScrollView(mDragLayer);
1458 dragController.setMoveTarget(mWorkspace);
1459 dragController.addDropTarget(mWorkspace);
1460 if (mSearchDropTargetBar != null) {
1461 mSearchDropTargetBar.setup(this, dragController);
1462 mSearchDropTargetBar.setQsbSearchBar(getOrCreateQsbBar());
1463 }
1464
1465 if (getResources().getBoolean(R.bool.debug_memory_enabled)) {
1466 Log.v(TAG, "adding WeightWatcher");
1467 mWeightWatcher = new WeightWatcher(this);
1468 mWeightWatcher.setAlpha(0.5f);
1469 ((FrameLayout) mLauncherView).addView(mWeightWatcher,
1470 new FrameLayout.LayoutParams(
1471 FrameLayout.LayoutParams.MATCH_PARENT,
1472 FrameLayout.LayoutParams.WRAP_CONTENT,
1473 Gravity.BOTTOM)
1474 );
1475
1476 boolean show = shouldShowWeightWatcher();
1477 mWeightWatcher.setVisibility(show ? View.VISIBLE : View.GONE);
1478 }
1479 }
1480
1481 /**
1482 * Sets the all apps button. This method is called from {@link Hotseat}.
1483 */
1484 public void setAllAppsButton(View allAppsButton) {
1485 mAllAppsButton = allAppsButton;
1486 }
1487
1488 public View getAllAppsButton() {
1489 return mAllAppsButton;
1490 }
1491
1492 /**
1493 * Creates a view representing a shortcut.
1494 *
1495 * @param info The data structure describing the shortcut.
1496 *
1497 * @return A View inflated from R.layout.application.
1498 */
1499 View createShortcut(ShortcutInfo info) {
1500 return createShortcut(R.layout.application,
1501 (ViewGroup) mWorkspace.getChildAt(mWorkspace.getCurrentPage()), info);
1502 }
1503
1504 /**
1505 * Creates a view representing a shortcut inflated from the specified resource.
1506 *
1507 * @param layoutResId The id of the XML layout used to create the shortcut.
1508 * @param parent The group the shortcut belongs to.
1509 * @param info The data structure describing the shortcut.
1510 *
1511 * @return A View inflated from layoutResId.
1512 */
1513 public View createShortcut(int layoutResId, ViewGroup parent, ShortcutInfo info) {
1514 BubbleTextView favorite = (BubbleTextView) mInflater.inflate(layoutResId, parent, false);
1515 favorite.applyFromShortcutInfo(info, mIconCache, true);
1516 favorite.setOnClickListener(this);
1517 favorite.setOnFocusChangeListener(mFocusHandler);
1518 return favorite;
1519 }
1520
1521 /**
1522 * Add a shortcut to the workspace.
1523 *
1524 * @param data The intent describing the shortcut.
1525 * @param cellInfo The position on screen where to create the shortcut.
1526 */
1527 private void completeAddShortcut(Intent data, long container, long screenId, int cellX,
1528 int cellY) {
1529 int[] cellXY = mTmpAddItemCellCoordinates;
1530 int[] touchXY = mPendingAddInfo.dropPos;
1531 CellLayout layout = getCellLayout(container, screenId);
1532
1533 boolean foundCellSpan = false;
1534
1535 ShortcutInfo info = mModel.infoFromShortcutIntent(this, data);
1536 if (info == null) {
1537 return;
1538 }
1539 final View view = createShortcut(info);
1540
1541 // First we check if we already know the exact location where we want to add this item.
1542 if (cellX >= 0 && cellY >= 0) {
1543 cellXY[0] = cellX;
1544 cellXY[1] = cellY;
1545 foundCellSpan = true;
1546
1547 // If appropriate, either create a folder or add to an existing folder
1548 if (mWorkspace.createUserFolderIfNecessary(view, container, layout, cellXY, 0,
1549 true, null,null)) {
1550 return;
1551 }
1552 DragObject dragObject = new DragObject();
1553 dragObject.dragInfo = info;
1554 if (mWorkspace.addToExistingFolderIfNecessary(view, layout, cellXY, 0, dragObject,
1555 true)) {
1556 return;
1557 }
1558 } else if (touchXY != null) {
1559 // when dragging and dropping, just find the closest free spot
1560 int[] result = layout.findNearestVacantArea(touchXY[0], touchXY[1], 1, 1, cellXY);
1561 foundCellSpan = (result != null);
1562 } else {
1563 foundCellSpan = layout.findCellForSpan(cellXY, 1, 1);
1564 }
1565
1566 if (!foundCellSpan) {
1567 showOutOfSpaceMessage(isHotseatLayout(layout));
1568 return;
1569 }
1570
1571 LauncherModel.addItemToDatabase(this, info, container, screenId, cellXY[0], cellXY[1]);
1572
1573 if (!mRestoring) {
1574 mWorkspace.addInScreen(view, container, screenId, cellXY[0], cellXY[1], 1, 1,
1575 isWorkspaceLocked());
1576 }
1577 }
1578
1579 static int[] getSpanForWidget(Context context, ComponentName component, int minWidth,
1580 int minHeight) {
1581 Rect padding = AppWidgetHostView.getDefaultPaddingForWidget(context, component, null);
1582 // We want to account for the extra amount of padding that we are adding to the widget
1583 // to ensure that it gets the full amount of space that it has requested
1584 int requiredWidth = minWidth + padding.left + padding.right;
1585 int requiredHeight = minHeight + padding.top + padding.bottom;
1586 return CellLayout.rectToCell(requiredWidth, requiredHeight, null);
1587 }
1588
1589 static int[] getSpanForWidget(Context context, AppWidgetProviderInfo info) {
1590 return getSpanForWidget(context, info.provider, info.minWidth, info.minHeight);
1591 }
1592
1593 static int[] getMinSpanForWidget(Context context, AppWidgetProviderInfo info) {
1594 return getSpanForWidget(context, info.provider, info.minResizeWidth, info.minResizeHeight);
1595 }
1596
1597 static int[] getSpanForWidget(Context context, PendingAddWidgetInfo info) {
1598 return getSpanForWidget(context, info.componentName, info.minWidth, info.minHeight);
1599 }
1600
1601 static int[] getMinSpanForWidget(Context context, PendingAddWidgetInfo info) {
1602 return getSpanForWidget(context, info.componentName, info.minResizeWidth,
1603 info.minResizeHeight);
1604 }
1605
1606 /**
1607 * Add a widget to the workspace.
1608 *
1609 * @param appWidgetId The app widget id
1610 */
1611 @Thunk void completeAddAppWidget(int appWidgetId, long container, long screenId,
1612 AppWidgetHostView hostView, LauncherAppWidgetProviderInfo appWidgetInfo) {
1613
1614 ItemInfo info = mPendingAddInfo;
1615 if (appWidgetInfo == null) {
1616 appWidgetInfo = LauncherAppWidgetProviderInfo.fromProviderInfo(this,
1617 mAppWidgetManager.getAppWidgetInfo(appWidgetId));
1618 }
1619
1620 if (appWidgetInfo.isCustomWidget) {
1621 appWidgetId = LauncherAppWidgetInfo.CUSTOM_WIDGET_ID;
1622 }
1623
1624 LauncherAppWidgetInfo launcherInfo;
1625 launcherInfo = new LauncherAppWidgetInfo(appWidgetId, appWidgetInfo.provider);
1626 launcherInfo.spanX = info.spanX;
1627 launcherInfo.spanY = info.spanY;
1628 launcherInfo.minSpanX = info.minSpanX;
1629 launcherInfo.minSpanY = info.minSpanY;
1630 launcherInfo.user = mAppWidgetManager.getUser(appWidgetInfo);
1631
1632 LauncherModel.addItemToDatabase(this, launcherInfo,
1633 container, screenId, info.cellX, info.cellY);
1634
1635 if (!mRestoring) {
1636 if (hostView == null) {
1637 // Perform actual inflation because we're live
1638 launcherInfo.hostView = mAppWidgetHost.createView(this, appWidgetId,
1639 appWidgetInfo);
1640 } else {
1641 // The AppWidgetHostView has already been inflated and instantiated
1642 launcherInfo.hostView = hostView;
1643 }
1644 launcherInfo.hostView.setTag(launcherInfo);
1645 launcherInfo.hostView.setVisibility(View.VISIBLE);
1646 launcherInfo.notifyWidgetSizeChanged(this);
1647
1648 mWorkspace.addInScreen(launcherInfo.hostView, container, screenId, info.cellX,
1649 info.cellY, launcherInfo.spanX, launcherInfo.spanY, isWorkspaceLocked());
1650
1651 addWidgetToAutoAdvanceIfNeeded(launcherInfo.hostView, appWidgetInfo);
1652 }
1653 resetAddInfo();
1654 }
1655
1656 private final BroadcastReceiver mReceiver = new BroadcastReceiver() {
1657 @Override
1658 public void onReceive(Context context, Intent intent) {
1659 final String action = intent.getAction();
1660 if (Intent.ACTION_SCREEN_OFF.equals(action)) {
1661 mUserPresent = false;
1662 mDragLayer.clearAllResizeFrames();
1663 updateAutoAdvanceState();
1664
1665 // Reset AllApps to its initial state only if we are not in the middle of
1666 // processing a multi-step drop
1667 if (mAppsView != null && mWidgetsView != null &&
1668 mPendingAddInfo.container == ItemInfo.NO_ID) {
1669 showWorkspace(false);
1670 }
1671 } else if (Intent.ACTION_USER_PRESENT.equals(action)) {
1672 mUserPresent = true;
1673 updateAutoAdvanceState();
1674 } else if (ENABLE_DEBUG_INTENTS && DebugIntents.DELETE_DATABASE.equals(action)) {
1675 mModel.resetLoadedState(false, true);
1676 mModel.startLoader(false, PagedView.INVALID_RESTORE_PAGE,
1677 LauncherModel.LOADER_FLAG_CLEAR_WORKSPACE);
1678 } else if (ENABLE_DEBUG_INTENTS && DebugIntents.MIGRATE_DATABASE.equals(action)) {
1679 mModel.resetLoadedState(false, true);
1680 mModel.startLoader(false, PagedView.INVALID_RESTORE_PAGE,
1681 LauncherModel.LOADER_FLAG_CLEAR_WORKSPACE
1682 | LauncherModel.LOADER_FLAG_MIGRATE_SHORTCUTS);
1683 } else if (LauncherAppsCompat.ACTION_MANAGED_PROFILE_ADDED.equals(action)
1684 || LauncherAppsCompat.ACTION_MANAGED_PROFILE_REMOVED.equals(action)) {
1685 getModel().forceReload();
1686 }
1687 }
1688 };
1689
1690 @Override
1691 public void onAttachedToWindow() {
1692 super.onAttachedToWindow();
1693
1694 // Listen for broadcasts related to user-presence
1695 final IntentFilter filter = new IntentFilter();
1696 filter.addAction(Intent.ACTION_SCREEN_OFF);
1697 filter.addAction(Intent.ACTION_USER_PRESENT);
1698 // For handling managed profiles
1699 filter.addAction(LauncherAppsCompat.ACTION_MANAGED_PROFILE_ADDED);
1700 filter.addAction(LauncherAppsCompat.ACTION_MANAGED_PROFILE_REMOVED);
1701 if (ENABLE_DEBUG_INTENTS) {
1702 filter.addAction(DebugIntents.DELETE_DATABASE);
1703 filter.addAction(DebugIntents.MIGRATE_DATABASE);
1704 }
1705 registerReceiver(mReceiver, filter);
1706 FirstFrameAnimatorHelper.initializeDrawListener(getWindow().getDecorView());
1707 setupTransparentSystemBarsForLmp();
1708 mAttached = true;
1709 mVisible = true;
1710 }
1711
1712 /**
1713 * Sets up transparent navigation and status bars in LMP.
1714 * This method is a no-op for other platform versions.
1715 */
1716 @TargetApi(Build.VERSION_CODES.LOLLIPOP)
1717 private void setupTransparentSystemBarsForLmp() {
1718 if (Utilities.isLmpOrAbove()) {
1719 Window window = getWindow();
1720 window.getAttributes().systemUiVisibility |=
1721 (View.SYSTEM_UI_FLAG_LAYOUT_STABLE
1722 | View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN
1723 | View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION);
1724 window.clearFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS
1725 | WindowManager.LayoutParams.FLAG_TRANSLUCENT_NAVIGATION);
1726 window.addFlags(WindowManager.LayoutParams.FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS);
1727 window.setStatusBarColor(Color.TRANSPARENT);
1728 window.setNavigationBarColor(Color.TRANSPARENT);
1729 }
1730 }
1731
1732 @Override
1733 public void onDetachedFromWindow() {
1734 super.onDetachedFromWindow();
1735 mVisible = false;
1736
1737 if (mAttached) {
1738 unregisterReceiver(mReceiver);
1739 mAttached = false;
1740 }
1741 updateAutoAdvanceState();
1742 }
1743
1744 public void onWindowVisibilityChanged(int visibility) {
1745 mVisible = visibility == View.VISIBLE;
1746 updateAutoAdvanceState();
1747 // The following code used to be in onResume, but it turns out onResume is called when
1748 // you're in All Apps and click home to go to the workspace. onWindowVisibilityChanged
1749 // is a more appropriate event to handle
1750 if (mVisible) {
1751 if (!mWorkspaceLoading) {
1752 final ViewTreeObserver observer = mWorkspace.getViewTreeObserver();
1753 // We want to let Launcher draw itself at least once before we force it to build
1754 // layers on all the workspace pages, so that transitioning to Launcher from other
1755 // apps is nice and speedy.
1756 observer.addOnDrawListener(new ViewTreeObserver.OnDrawListener() {
1757 private boolean mStarted = false;
1758 public void onDraw() {
1759 if (mStarted) return;
1760 mStarted = true;
1761 // We delay the layer building a bit in order to give
1762 // other message processing a time to run. In particular
1763 // this avoids a delay in hiding the IME if it was
1764 // currently shown, because doing that may involve
1765 // some communication back with the app.
1766 mWorkspace.postDelayed(mBuildLayersRunnable, 500);
1767 final ViewTreeObserver.OnDrawListener listener = this;
1768 mWorkspace.post(new Runnable() {
1769 public void run() {
1770 if (mWorkspace != null &&
1771 mWorkspace.getViewTreeObserver() != null) {
1772 mWorkspace.getViewTreeObserver().
1773 removeOnDrawListener(listener);
1774 }
1775 }
1776 });
1777 return;
1778 }
1779 });
1780 }
1781 clearTypedText();
1782 }
1783 }
1784
1785 @Thunk void sendAdvanceMessage(long delay) {
1786 mHandler.removeMessages(ADVANCE_MSG);
1787 Message msg = mHandler.obtainMessage(ADVANCE_MSG);
1788 mHandler.sendMessageDelayed(msg, delay);
1789 mAutoAdvanceSentTime = System.currentTimeMillis();
1790 }
1791
1792 @Thunk void updateAutoAdvanceState() {
1793 boolean autoAdvanceRunning = mVisible && mUserPresent && !mWidgetsToAdvance.isEmpty();
1794 if (autoAdvanceRunning != mAutoAdvanceRunning) {
1795 mAutoAdvanceRunning = autoAdvanceRunning;
1796 if (autoAdvanceRunning) {
1797 long delay = mAutoAdvanceTimeLeft == -1 ? mAdvanceInterval : mAutoAdvanceTimeLeft;
1798 sendAdvanceMessage(delay);
1799 } else {
1800 if (!mWidgetsToAdvance.isEmpty()) {
1801 mAutoAdvanceTimeLeft = Math.max(0, mAdvanceInterval -
1802 (System.currentTimeMillis() - mAutoAdvanceSentTime));
1803 }
1804 mHandler.removeMessages(ADVANCE_MSG);
1805 mHandler.removeMessages(0); // Remove messages sent using postDelayed()
1806 }
1807 }
1808 }
1809
1810 private final Handler mHandler = new Handler() {
1811 @Override
1812 public void handleMessage(Message msg) {
1813 if (msg.what == ADVANCE_MSG) {
1814 int i = 0;
1815 for (View key: mWidgetsToAdvance.keySet()) {
1816 final View v = key.findViewById(mWidgetsToAdvance.get(key).autoAdvanceViewId);
1817 final int delay = mAdvanceStagger * i;
1818 if (v instanceof Advanceable) {
1819 postDelayed(new Runnable() {
1820 public void run() {
1821 ((Advanceable) v).advance();
1822 }
1823 }, delay);
1824 }
1825 i++;
1826 }
1827 sendAdvanceMessage(mAdvanceInterval);
1828 }
1829 }
1830 };
1831
1832 void addWidgetToAutoAdvanceIfNeeded(View hostView, AppWidgetProviderInfo appWidgetInfo) {
1833 if (appWidgetInfo == null || appWidgetInfo.autoAdvanceViewId == -1) return;
1834 View v = hostView.findViewById(appWidgetInfo.autoAdvanceViewId);
1835 if (v instanceof Advanceable) {
1836 mWidgetsToAdvance.put(hostView, appWidgetInfo);
1837 ((Advanceable) v).fyiWillBeAdvancedByHostKThx();
1838 updateAutoAdvanceState();
1839 }
1840 }
1841
1842 void removeWidgetToAutoAdvance(View hostView) {
1843 if (mWidgetsToAdvance.containsKey(hostView)) {
1844 mWidgetsToAdvance.remove(hostView);
1845 updateAutoAdvanceState();
1846 }
1847 }
1848
1849 public void removeAppWidget(LauncherAppWidgetInfo launcherInfo) {
1850 removeWidgetToAutoAdvance(launcherInfo.hostView);
1851 launcherInfo.hostView = null;
1852 }
1853
1854 public void showOutOfSpaceMessage(boolean isHotseatLayout) {
1855 int strId = (isHotseatLayout ? R.string.hotseat_out_of_space : R.string.out_of_space);
1856 Toast.makeText(this, getString(strId), Toast.LENGTH_SHORT).show();
1857 }
1858
1859 public DragLayer getDragLayer() {
1860 return mDragLayer;
1861 }
1862
1863 public AppsContainerView getAppsView() {
1864 return mAppsView;
1865 }
1866
1867 public WidgetsContainerView getWidgetsView() {
1868 return mWidgetsView;
1869 }
1870
1871 public Workspace getWorkspace() {
1872 return mWorkspace;
1873 }
1874
1875 public Hotseat getHotseat() {
1876 return mHotseat;
1877 }
1878
1879 public ViewGroup getOverviewPanel() {
1880 return mOverviewPanel;
1881 }
1882
1883 public SearchDropTargetBar getSearchBar() {
1884 return mSearchDropTargetBar;
1885 }
1886
1887 public LauncherAppWidgetHost getAppWidgetHost() {
1888 return mAppWidgetHost;
1889 }
1890
1891 public LauncherModel getModel() {
1892 return mModel;
1893 }
1894
1895 protected SharedPreferences getSharedPrefs() {
1896 return mSharedPrefs;
1897 }
1898
1899 public void closeSystemDialogs() {
1900 getWindow().closeAllPanels();
1901
1902 // Whatever we were doing is hereby canceled.
1903 setWaitingForResult(false);
1904 }
1905
1906 @Override
1907 protected void onNewIntent(Intent intent) {
1908 long startTime = 0;
1909 if (DEBUG_RESUME_TIME) {
1910 startTime = System.currentTimeMillis();
1911 }
1912 super.onNewIntent(intent);
1913
1914 // Close the menu
1915 if (Intent.ACTION_MAIN.equals(intent.getAction())) {
1916 // also will cancel mWaitingForResult.
1917 closeSystemDialogs();
1918
1919 final boolean alreadyOnHome = mHasFocus && ((intent.getFlags() &
1920 Intent.FLAG_ACTIVITY_BROUGHT_TO_FRONT)
1921 != Intent.FLAG_ACTIVITY_BROUGHT_TO_FRONT);
1922
1923 if (mWorkspace == null) {
1924 // Can be cases where mWorkspace is null, this prevents a NPE
1925 return;
1926 }
1927 Folder openFolder = mWorkspace.getOpenFolder();
1928 // In all these cases, only animate if we're already on home
1929 mWorkspace.exitWidgetResizeMode();
1930
1931 boolean moveToDefaultScreen = mLauncherCallbacks != null ?
1932 mLauncherCallbacks.shouldMoveToDefaultScreenOnHomeIntent() : true;
1933 if (alreadyOnHome && mState == State.WORKSPACE && !mWorkspace.isTouchActive() &&
1934 openFolder == null && moveToDefaultScreen) {
1935 mWorkspace.moveToDefaultScreen(true);
1936 }
1937
1938 closeFolder();
1939 exitSpringLoadedDragMode();
1940
1941 // If we are already on home, then just animate back to the workspace,
1942 // otherwise, just wait until onResume to set the state back to Workspace
1943 if (alreadyOnHome) {
1944 showWorkspace(true);
1945 } else {
1946 mOnResumeState = State.WORKSPACE;
1947 }
1948
1949 final View v = getWindow().peekDecorView();
1950 if (v != null && v.getWindowToken() != null) {
1951 InputMethodManager imm = (InputMethodManager)getSystemService(
1952 INPUT_METHOD_SERVICE);
1953 imm.hideSoftInputFromWindow(v.getWindowToken(), 0);
1954 }
1955
1956 // Reset the apps view
1957 if (!alreadyOnHome && mAppsView != null) {
1958 mAppsView.scrollToTop();
1959 }
1960
1961 // Reset the widgets view
1962 if (!alreadyOnHome && mWidgetsView != null) {
1963 mWidgetsView.scrollToTop();
1964 }
1965
1966 if (mLauncherCallbacks != null) {
1967 mLauncherCallbacks.onHomeIntent();
1968 }
1969 }
1970
1971 if (DEBUG_RESUME_TIME) {
1972 Log.d(TAG, "Time spent in onNewIntent: " + (System.currentTimeMillis() - startTime));
1973 }
1974
1975 if (mLauncherCallbacks != null) {
1976 mLauncherCallbacks.onNewIntent(intent);
1977 }
1978 }
1979
1980 @Override
1981 public void onRestoreInstanceState(Bundle state) {
1982 super.onRestoreInstanceState(state);
1983 for (int page: mSynchronouslyBoundPages) {
1984 mWorkspace.restoreInstanceStateForChild(page);
1985 }
1986 }
1987
1988 @Override
1989 protected void onSaveInstanceState(Bundle outState) {
1990 if (mWorkspace.getChildCount() > 0) {
1991 outState.putInt(RUNTIME_STATE_CURRENT_SCREEN,
1992 mWorkspace.getCurrentPageOffsetFromCustomContent());
1993 }
1994 super.onSaveInstanceState(outState);
1995
1996 outState.putInt(RUNTIME_STATE, mState.ordinal());
1997 // We close any open folder since it will not be re-opened, and we need to make sure
1998 // this state is reflected.
1999 closeFolder();
2000
2001 if (mPendingAddInfo.container != ItemInfo.NO_ID && mPendingAddInfo.screenId > -1 &&
2002 mWaitingForResult) {
2003 outState.putLong(RUNTIME_STATE_PENDING_ADD_CONTAINER, mPendingAddInfo.container);
2004 outState.putLong(RUNTIME_STATE_PENDING_ADD_SCREEN, mPendingAddInfo.screenId);
2005 outState.putInt(RUNTIME_STATE_PENDING_ADD_CELL_X, mPendingAddInfo.cellX);
2006 outState.putInt(RUNTIME_STATE_PENDING_ADD_CELL_Y, mPendingAddInfo.cellY);
2007 outState.putInt(RUNTIME_STATE_PENDING_ADD_SPAN_X, mPendingAddInfo.spanX);
2008 outState.putInt(RUNTIME_STATE_PENDING_ADD_SPAN_Y, mPendingAddInfo.spanY);
2009 outState.putParcelable(RUNTIME_STATE_PENDING_ADD_WIDGET_INFO, mPendingAddWidgetInfo);
2010 outState.putInt(RUNTIME_STATE_PENDING_ADD_WIDGET_ID, mPendingAddWidgetId);
2011 }
2012
2013 if (mFolderInfo != null && mWaitingForResult) {
2014 outState.putBoolean(RUNTIME_STATE_PENDING_FOLDER_RENAME, true);
2015 outState.putLong(RUNTIME_STATE_PENDING_FOLDER_RENAME_ID, mFolderInfo.id);
2016 }
2017
2018 // Save the current widgets tray?
2019 // TODO(hyunyoungs)
2020 outState.putSerializable(RUNTIME_STATE_VIEW_IDS, mItemIdToViewId);
2021
2022 if (mLauncherCallbacks != null) {
2023 mLauncherCallbacks.onSaveInstanceState(outState);
2024 }
2025 }
2026
2027 @Override
2028 public void onDestroy() {
2029 super.onDestroy();
2030
2031 // Remove all pending runnables
2032 mHandler.removeMessages(ADVANCE_MSG);
2033 mHandler.removeMessages(0);
2034 mWorkspace.removeCallbacks(mBuildLayersRunnable);
2035
2036 // Stop callbacks from LauncherModel
2037 LauncherAppState app = (LauncherAppState.getInstance());
2038
2039 // It's possible to receive onDestroy after a new Launcher activity has
2040 // been created. In this case, don't interfere with the new Launcher.
2041 if (mModel.isCurrentCallbacks(this)) {
2042 mModel.stopLoader();
2043 app.setLauncher(null);
2044 }
2045
2046 try {
2047 mAppWidgetHost.stopListening();
2048 } catch (NullPointerException ex) {
2049 Log.w(TAG, "problem while stopping AppWidgetHost during Launcher destruction", ex);
2050 }
2051 mAppWidgetHost = null;
2052
2053 mWidgetsToAdvance.clear();
2054
2055 TextKeyListener.getInstance().release();
2056
2057 // Disconnect any of the callbacks and drawables associated with ItemInfos on the workspace
2058 // to prevent leaking Launcher activities on orientation change.
2059 if (mModel != null) {
2060 mModel.unbindItemInfosAndClearQueuedBindRunnables();
2061 }
2062
2063 getContentResolver().unregisterContentObserver(mWidgetObserver);
2064 unregisterReceiver(mCloseSystemDialogsReceiver);
2065
2066 mDragLayer.clearAllResizeFrames();
2067 ((ViewGroup) mWorkspace.getParent()).removeAllViews();
2068 mWorkspace.removeAllWorkspaceScreens();
2069 mWorkspace = null;
2070 mDragController = null;
2071
2072 LauncherAnimUtils.onDestroyActivity();
2073
2074 if (mLauncherCallbacks != null) {
2075 mLauncherCallbacks.onDestroy();
2076 }
2077 }
2078
2079 public DragController getDragController() {
2080 return mDragController;
2081 }
2082
2083 @Override
2084 public void startActivityForResult(Intent intent, int requestCode) {
2085 onStartForResult(requestCode);
2086 super.startActivityForResult(intent, requestCode);
2087 }
2088
2089 @Override
2090 public void startIntentSenderForResult (IntentSender intent, int requestCode,
2091 Intent fillInIntent, int flagsMask, int flagsValues, int extraFlags, Bundle options) {
2092 onStartForResult(requestCode);
2093 try {
2094 super.startIntentSenderForResult(intent, requestCode,
2095 fillInIntent, flagsMask, flagsValues, extraFlags, options);
2096 } catch (IntentSender.SendIntentException e) {
2097 throw new ActivityNotFoundException();
2098 }
2099 }
2100
2101 private void onStartForResult(int requestCode) {
2102 if (requestCode >= 0) {
2103 setWaitingForResult(true);
2104 }
2105 }
2106
2107 /**
2108 * Indicates that we want global search for this activity by setting the globalSearch
2109 * argument for {@link #startSearch} to true.
2110 */
2111 @Override
2112 public void startSearch(String initialQuery, boolean selectInitialQuery,
2113 Bundle appSearchData, boolean globalSearch) {
2114
2115 showWorkspace(true);
2116
2117 if (initialQuery == null) {
2118 // Use any text typed in the launcher as the initial query
2119 initialQuery = getTypedText();
2120 }
2121 if (appSearchData == null) {
2122 appSearchData = new Bundle();
2123 appSearchData.putString("source", "launcher-search");
2124 }
2125 Rect sourceBounds = new Rect();
2126 if (mSearchDropTargetBar != null) {
2127 sourceBounds = mSearchDropTargetBar.getSearchBarBounds();
2128 }
2129
2130 boolean clearTextImmediately = startSearch(initialQuery, selectInitialQuery,
2131 appSearchData, sourceBounds);
2132 if (clearTextImmediately) {
2133 clearTypedText();
2134 }
2135 }
2136
2137 /**
2138 * Start a text search.
2139 *
2140 * @return {@code true} if the search will start immediately, so any further keypresses
2141 * will be handled directly by the search UI. {@code false} if {@link Launcher} should continue
2142 * to buffer keypresses.
2143 */
2144 public boolean startSearch(String initialQuery,
2145 boolean selectInitialQuery, Bundle appSearchData, Rect sourceBounds) {
2146 if (mLauncherCallbacks != null && mLauncherCallbacks.providesSearch()) {
2147 return mLauncherCallbacks.startSearch(initialQuery, selectInitialQuery, appSearchData,
2148 sourceBounds);
2149 }
2150
2151 startGlobalSearch(initialQuery, selectInitialQuery,
2152 appSearchData, sourceBounds);
2153 return false;
2154 }
2155
2156 /**
2157 * Starts the global search activity. This code is a copied from SearchManager
2158 */
2159 private void startGlobalSearch(String initialQuery,
2160 boolean selectInitialQuery, Bundle appSearchData, Rect sourceBounds) {
2161 final SearchManager searchManager =
2162 (SearchManager) getSystemService(Context.SEARCH_SERVICE);
2163 ComponentName globalSearchActivity = searchManager.getGlobalSearchActivity();
2164 if (globalSearchActivity == null) {
2165 Log.w(TAG, "No global search activity found.");
2166 return;
2167 }
2168 Intent intent = new Intent(SearchManager.INTENT_ACTION_GLOBAL_SEARCH);
2169 intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
2170 intent.setComponent(globalSearchActivity);
2171 // Make sure that we have a Bundle to put source in
2172 if (appSearchData == null) {
2173 appSearchData = new Bundle();
2174 } else {
2175 appSearchData = new Bundle(appSearchData);
2176 }
2177 // Set source to package name of app that starts global search if not set already.
2178 if (!appSearchData.containsKey("source")) {
2179 appSearchData.putString("source", getPackageName());
2180 }
2181 intent.putExtra(SearchManager.APP_DATA, appSearchData);
2182 if (!TextUtils.isEmpty(initialQuery)) {
2183 intent.putExtra(SearchManager.QUERY, initialQuery);
2184 }
2185 if (selectInitialQuery) {
2186 intent.putExtra(SearchManager.EXTRA_SELECT_QUERY, selectInitialQuery);
2187 }
2188 intent.setSourceBounds(sourceBounds);
2189 try {
2190 startActivity(intent);
2191 } catch (ActivityNotFoundException ex) {
2192 Log.e(TAG, "Global search activity not found: " + globalSearchActivity);
2193 }
2194 }
2195
2196 public boolean isOnCustomContent() {
2197 return mWorkspace.isOnOrMovingToCustomContent();
2198 }
2199
2200 @Override
2201 public boolean onPrepareOptionsMenu(Menu menu) {
2202 super.onPrepareOptionsMenu(menu);
2203 if (!isOnCustomContent()) {
2204 // Close any open folders
2205 closeFolder();
2206 // Stop resizing any widgets
2207 mWorkspace.exitWidgetResizeMode();
2208 if (!mWorkspace.isInOverviewMode()) {
2209 // Show the overview mode
2210 showOverviewMode(true);
2211 } else {
2212 showWorkspace(true);
2213 }
2214 }
2215 if (mLauncherCallbacks != null) {
2216 return mLauncherCallbacks.onPrepareOptionsMenu(menu);
2217 }
2218
2219 return false;
2220 }
2221
2222 @Override
2223 public boolean onSearchRequested() {
2224 startSearch(null, false, null, true);
2225 // Use a custom animation for launching search
2226 return true;
2227 }
2228
2229 public boolean isWorkspaceLocked() {
2230 return mWorkspaceLoading || mWaitingForResult;
2231 }
2232
2233 public boolean isWorkspaceLoading() {
2234 return mWorkspaceLoading;
2235 }
2236
2237 private void setWorkspaceLoading(boolean value) {
2238 boolean isLocked = isWorkspaceLocked();
2239 mWorkspaceLoading = value;
2240 if (isLocked != isWorkspaceLocked()) {
2241 onWorkspaceLockedChanged();
2242 }
2243 }
2244
2245 private void setWaitingForResult(boolean value) {
2246 boolean isLocked = isWorkspaceLocked();
2247 mWaitingForResult = value;
2248 if (isLocked != isWorkspaceLocked()) {
2249 onWorkspaceLockedChanged();
2250 }
2251 }
2252
2253 protected void onWorkspaceLockedChanged() {
2254 if (mLauncherCallbacks != null) {
2255 mLauncherCallbacks.onWorkspaceLockedChanged();
2256 }
2257 }
2258
2259 private void resetAddInfo() {
2260 mPendingAddInfo.container = ItemInfo.NO_ID;
2261 mPendingAddInfo.screenId = -1;
2262 mPendingAddInfo.cellX = mPendingAddInfo.cellY = -1;
2263 mPendingAddInfo.spanX = mPendingAddInfo.spanY = -1;
2264 mPendingAddInfo.minSpanX = mPendingAddInfo.minSpanY = -1;
2265 mPendingAddInfo.dropPos = null;
2266 }
2267
2268 void addAppWidgetImpl(final int appWidgetId, final ItemInfo info, final
2269 AppWidgetHostView boundWidget, final LauncherAppWidgetProviderInfo appWidgetInfo) {
2270 addAppWidgetImpl(appWidgetId, info, boundWidget, appWidgetInfo, 0);
2271 }
2272
2273 void addAppWidgetImpl(final int appWidgetId, final ItemInfo info,
2274 final AppWidgetHostView boundWidget, final LauncherAppWidgetProviderInfo appWidgetInfo,
2275 int delay) {
2276 if (appWidgetInfo.configure != null) {
2277 mPendingAddWidgetInfo = appWidgetInfo;
2278 mPendingAddWidgetId = appWidgetId;
2279
2280 // Launch over to configure widget, if needed
2281 mAppWidgetManager.startConfigActivity(appWidgetInfo, appWidgetId, this,
2282 mAppWidgetHost, REQUEST_CREATE_APPWIDGET);
2283
2284 } else {
2285 // Otherwise just add it
2286 Runnable onComplete = new Runnable() {
2287 @Override
2288 public void run() {
2289 // Exit spring loaded mode if necessary after adding the widget
2290 exitSpringLoadedDragModeDelayed(true, EXIT_SPRINGLOADED_MODE_SHORT_TIMEOUT,
2291 null);
2292 }
2293 };
2294 completeAddAppWidget(appWidgetId, info.container, info.screenId, boundWidget,
2295 appWidgetInfo);
2296 mWorkspace.removeExtraEmptyScreenDelayed(true, onComplete, delay, false);
2297 }
2298 }
2299
2300 protected void moveToCustomContentScreen(boolean animate) {
2301 // Close any folders that may be open.
2302 closeFolder();
2303 mWorkspace.moveToCustomContentScreen(animate);
2304 }
2305
2306 public void addPendingItem(PendingAddItemInfo info, long container, long screenId,
2307 int[] cell, int spanX, int spanY) {
2308 switch (info.itemType) {
2309 case LauncherSettings.Favorites.ITEM_TYPE_CUSTOM_APPWIDGET:
2310 case LauncherSettings.Favorites.ITEM_TYPE_APPWIDGET:
2311 int span[] = new int[2];
2312 span[0] = spanX;
2313 span[1] = spanY;
2314 addAppWidgetFromDrop((PendingAddWidgetInfo) info,
2315 container, screenId, cell, span);
2316 break;
2317 case LauncherSettings.Favorites.ITEM_TYPE_SHORTCUT:
2318 processShortcutFromDrop(info.componentName, container, screenId, cell);
2319 break;
2320 default:
2321 throw new IllegalStateException("Unknown item type: " + info.itemType);
2322 }
2323 }
2324
2325 /**
2326 * Process a shortcut drop.
2327 *
2328 * @param componentName The name of the component
2329 * @param screenId The ID of the screen where it should be added
2330 * @param cell The cell it should be added to, optional
2331 */
2332 private void processShortcutFromDrop(ComponentName componentName, long container, long screenId,
2333 int[] cell) {
2334 resetAddInfo();
2335 mPendingAddInfo.container = container;
2336 mPendingAddInfo.screenId = screenId;
2337 mPendingAddInfo.dropPos = null;
2338
2339 if (cell != null) {
2340 mPendingAddInfo.cellX = cell[0];
2341 mPendingAddInfo.cellY = cell[1];
2342 }
2343
2344 Intent createShortcutIntent = new Intent(Intent.ACTION_CREATE_SHORTCUT);
2345 createShortcutIntent.setComponent(componentName);
2346 processShortcut(createShortcutIntent);
2347 }
2348
2349 /**
2350 * Process a widget drop.
2351 *
2352 * @param info The PendingAppWidgetInfo of the widget being added.
2353 * @param screenId The ID of the screen where it should be added
2354 * @param cell The cell it should be added to, optional
2355 */
2356 private void addAppWidgetFromDrop(PendingAddWidgetInfo info, long container, long screenId,
2357 int[] cell, int[] span) {
2358 resetAddInfo();
2359 mPendingAddInfo.container = info.container = container;
2360 mPendingAddInfo.screenId = info.screenId = screenId;
2361 mPendingAddInfo.dropPos = null;
2362 mPendingAddInfo.minSpanX = info.minSpanX;
2363 mPendingAddInfo.minSpanY = info.minSpanY;
2364
2365 if (cell != null) {
2366 mPendingAddInfo.cellX = cell[0];
2367 mPendingAddInfo.cellY = cell[1];
2368 }
2369 if (span != null) {
2370 mPendingAddInfo.spanX = span[0];
2371 mPendingAddInfo.spanY = span[1];
2372 }
2373
2374 AppWidgetHostView hostView = info.boundWidget;
2375 int appWidgetId;
2376 if (hostView != null) {
2377 appWidgetId = hostView.getAppWidgetId();
2378 addAppWidgetImpl(appWidgetId, info, hostView, info.info);
2379 } else {
2380 // In this case, we either need to start an activity to get permission to bind
2381 // the widget, or we need to start an activity to configure the widget, or both.
2382 appWidgetId = getAppWidgetHost().allocateAppWidgetId();
2383 Bundle options = info.bindOptions;
2384
2385 boolean success = mAppWidgetManager.bindAppWidgetIdIfAllowed(
2386 appWidgetId, info.info, options);
2387 if (success) {
2388 addAppWidgetImpl(appWidgetId, info, null, info.info);
2389 } else {
2390 mPendingAddWidgetInfo = info.info;
2391 Intent intent = new Intent(AppWidgetManager.ACTION_APPWIDGET_BIND);
2392 intent.putExtra(AppWidgetManager.EXTRA_APPWIDGET_ID, appWidgetId);
2393 intent.putExtra(AppWidgetManager.EXTRA_APPWIDGET_PROVIDER, info.componentName);
2394 mAppWidgetManager.getUser(mPendingAddWidgetInfo)
2395 .addToIntent(intent, AppWidgetManager.EXTRA_APPWIDGET_PROVIDER_PROFILE);
2396 // TODO: we need to make sure that this accounts for the options bundle.
2397 // intent.putExtra(AppWidgetManager.EXTRA_APPWIDGET_OPTIONS, options);
2398 startActivityForResult(intent, REQUEST_BIND_APPWIDGET);
2399 }
2400 }
2401 }
2402
2403 void processShortcut(Intent intent) {
2404 Utilities.startActivityForResultSafely(this, intent, REQUEST_CREATE_SHORTCUT);
2405 }
2406
2407 void processWallpaper(Intent intent) {
2408 startActivityForResult(intent, REQUEST_PICK_WALLPAPER);
2409 }
2410
2411 FolderIcon addFolder(CellLayout layout, long container, final long screenId, int cellX,
2412 int cellY) {
2413 final FolderInfo folderInfo = new FolderInfo();
2414 folderInfo.title = getText(R.string.folder_name);
2415
2416 // Update the model
2417 LauncherModel.addItemToDatabase(Launcher.this, folderInfo, container, screenId,
2418 cellX, cellY);
2419 sFolders.put(folderInfo.id, folderInfo);
2420
2421 // Create the view
2422 FolderIcon newFolder =
2423 FolderIcon.fromXml(R.layout.folder_icon, this, layout, folderInfo, mIconCache);
2424 mWorkspace.addInScreen(newFolder, container, screenId, cellX, cellY, 1, 1,
2425 isWorkspaceLocked());
2426 // Force measure the new folder icon
2427 CellLayout parent = mWorkspace.getParentCellLayoutForView(newFolder);
2428 parent.getShortcutsAndWidgets().measureChild(newFolder);
2429 return newFolder;
2430 }
2431
2432 void removeFolder(FolderInfo folder) {
2433 sFolders.remove(folder.id);
2434 }
2435
2436 /**
2437 * Registers various content observers. The current implementation registers
2438 * only a favorites observer to keep track of the favorites applications.
2439 */
2440 private void registerContentObservers() {
2441 ContentResolver resolver = getContentResolver();
2442 resolver.registerContentObserver(LauncherProvider.CONTENT_APPWIDGET_RESET_URI,
2443 true, mWidgetObserver);
2444 }
2445
2446 @Override
2447 public boolean dispatchKeyEvent(KeyEvent event) {
2448 if (event.getAction() == KeyEvent.ACTION_DOWN) {
2449 switch (event.getKeyCode()) {
2450 case KeyEvent.KEYCODE_HOME:
2451 return true;
2452 case KeyEvent.KEYCODE_VOLUME_DOWN:
2453 if (Utilities.isPropertyEnabled(DUMP_STATE_PROPERTY)) {
2454 dumpState();
2455 return true;
2456 }
2457 break;
2458 }
2459 } else if (event.getAction() == KeyEvent.ACTION_UP) {
2460 switch (event.getKeyCode()) {
2461 case KeyEvent.KEYCODE_HOME:
2462 return true;
2463 }
2464 }
2465
2466 return super.dispatchKeyEvent(event);
2467 }
2468
2469 @Override
2470 public void onBackPressed() {
2471 if (mLauncherCallbacks != null && mLauncherCallbacks.handleBackPressed()) {
2472 return;
2473 }
2474
2475 LauncherAccessibilityDelegate delegate =
2476 LauncherAppState.getInstance().getAccessibilityDelegate();
2477 if (delegate != null && delegate.onBackPressed()) {
2478 return;
2479 }
2480
2481 if (isAppsViewVisible()) {
2482 showWorkspace(true);
2483 } else if (isWidgetsViewVisible()) {
2484 showOverviewMode(true);
2485 } else if (mWorkspace.isInOverviewMode()) {
2486 mWorkspace.exitOverviewMode(true);
2487 } else if (mWorkspace.getOpenFolder() != null) {
2488 Folder openFolder = mWorkspace.getOpenFolder();
2489 if (openFolder.isEditingName()) {
2490 openFolder.dismissEditingName();
2491 } else {
2492 closeFolder();
2493 }
2494 } else {
2495 mWorkspace.exitWidgetResizeMode();
2496
2497 // Back button is a no-op here, but give at least some feedback for the button press
2498 mWorkspace.showOutlinesTemporarily();
2499 }
2500 }
2501
2502 /**
2503 * Re-listen when widgets are reset.
2504 */
2505 @Thunk void onAppWidgetReset() {
2506 if (mAppWidgetHost != null) {
2507 mAppWidgetHost.startListening();
2508 }
2509 }
2510
2511 /**
2512 * Launches the intent referred by the clicked shortcut.
2513 *
2514 * @param v The view representing the clicked shortcut.
2515 */
2516 public void onClick(View v) {
2517 // Make sure that rogue clicks don't get through while allapps is launching, or after the
2518 // view has detached (it's possible for this to happen if the view is removed mid touch).
2519 if (v.getWindowToken() == null) {
2520 return;
2521 }
2522
2523 if (!mWorkspace.isFinishedSwitchingState()) {
2524 return;
2525 }
2526
2527 if (v instanceof Workspace) {
2528 if (mWorkspace.isInOverviewMode()) {
2529 mWorkspace.exitOverviewMode(true);
2530 }
2531 return;
2532 }
2533
2534 if (v instanceof CellLayout) {
2535 if (mWorkspace.isInOverviewMode()) {
2536 mWorkspace.exitOverviewMode(mWorkspace.indexOfChild(v), true);
2537 }
2538 }
2539
2540 Object tag = v.getTag();
2541 if (tag instanceof ShortcutInfo) {
2542 onClickAppShortcut(v);
2543 } else if (tag instanceof FolderInfo) {
2544 if (v instanceof FolderIcon) {
2545 onClickFolderIcon(v);
2546 }
2547 } else if (v == mAllAppsButton) {
2548 onClickAllAppsButton(v);
2549 } else if (tag instanceof AppInfo) {
2550 startAppShortcutOrInfoActivity(v);
2551 } else if (tag instanceof LauncherAppWidgetInfo) {
2552 if (v instanceof PendingAppWidgetHostView) {
2553 onClickPendingWidget((PendingAppWidgetHostView) v);
2554 }
2555 }
2556 }
2557
2558 public void onClickPagedViewIcon(View v) {
2559 startAppShortcutOrInfoActivity(v);
2560 if (mLauncherCallbacks != null) {
2561 mLauncherCallbacks.onClickPagedViewIcon(v);
2562 }
2563 }
2564
2565 public boolean onTouch(View v, MotionEvent event) {
2566 return false;
2567 }
2568
2569 /**
2570 * Event handler for the app widget view which has not fully restored.
2571 */
2572 public void onClickPendingWidget(final PendingAppWidgetHostView v) {
2573 if (mIsSafeModeEnabled) {
2574 Toast.makeText(this, R.string.safemode_widget_error, Toast.LENGTH_SHORT).show();
2575 return;
2576 }
2577
2578 final LauncherAppWidgetInfo info = (LauncherAppWidgetInfo) v.getTag();
2579 if (v.isReadyForClickSetup()) {
2580 int widgetId = info.appWidgetId;
2581 AppWidgetProviderInfo appWidgetInfo = mAppWidgetManager.getAppWidgetInfo(widgetId);
2582 if (appWidgetInfo != null) {
2583 mPendingAddWidgetInfo = LauncherAppWidgetProviderInfo.fromProviderInfo(
2584 this, appWidgetInfo);
2585 mPendingAddInfo.copyFrom(info);
2586 mPendingAddWidgetId = widgetId;
2587
2588 AppWidgetManagerCompat.getInstance(this).startConfigActivity(appWidgetInfo,
2589 info.appWidgetId, this, mAppWidgetHost, REQUEST_RECONFIGURE_APPWIDGET);
2590 }
2591 } else if (info.installProgress < 0) {
2592 // The install has not been queued
2593 final String packageName = info.providerName.getPackageName();
2594 showBrokenAppInstallDialog(packageName,
2595 new DialogInterface.OnClickListener() {
2596 public void onClick(DialogInterface dialog, int id) {
2597 startActivitySafely(v, LauncherModel.getMarketIntent(packageName), info);
2598 }
2599 });
2600 } else {
2601 // Download has started.
2602 final String packageName = info.providerName.getPackageName();
2603 startActivitySafely(v, LauncherModel.getMarketIntent(packageName), info);
2604 }
2605 }
2606
2607 /**
2608 * Event handler for the "grid" button that appears on the home screen, which
2609 * enters all apps mode.
2610 *
2611 * @param v The view that was clicked.
2612 */
2613 protected void onClickAllAppsButton(View v) {
2614 if (LOGD) Log.d(TAG, "onClickAllAppsButton");
2615 if (isAppsViewVisible()) {
2616 showWorkspace(true);
2617 } else {
2618 showAppsView(true /* animated */, false /* resetListToTop */);
2619 }
2620 }
2621
2622 private void showBrokenAppInstallDialog(final String packageName,
2623 DialogInterface.OnClickListener onSearchClickListener) {
2624 new AlertDialog.Builder(this)
2625 .setTitle(R.string.abandoned_promises_title)
2626 .setMessage(R.string.abandoned_promise_explanation)
2627 .setPositiveButton(R.string.abandoned_search, onSearchClickListener)
2628 .setNeutralButton(R.string.abandoned_clean_this,
2629 new DialogInterface.OnClickListener() {
2630 public void onClick(DialogInterface dialog, int id) {
2631 final UserHandleCompat user = UserHandleCompat.myUserHandle();
2632 mWorkspace.removeAbandonedPromise(packageName, user);
2633 }
2634 })
2635 .create().show();
2636 return;
2637 }
2638
2639 /**
2640 * Event handler for an app shortcut click.
2641 *
2642 * @param v The view that was clicked. Must be a tagged with a {@link ShortcutInfo}.
2643 */
2644 protected void onClickAppShortcut(final View v) {
2645 if (LOGD) Log.d(TAG, "onClickAppShortcut");
2646 Object tag = v.getTag();
2647 if (!(tag instanceof ShortcutInfo)) {
2648 throw new IllegalArgumentException("Input must be a Shortcut");
2649 }
2650
2651 // Open shortcut
2652 final ShortcutInfo shortcut = (ShortcutInfo) tag;
2653
2654 if (shortcut.isDisabled != 0) {
2655 int error = R.string.activity_not_available;
2656 if ((shortcut.isDisabled & ShortcutInfo.FLAG_DISABLED_SAFEMODE) != 0) {
2657 error = R.string.safemode_shortcut_error;
2658 }
2659 Toast.makeText(this, error, Toast.LENGTH_SHORT).show();
2660 return;
2661 }
2662
2663 final Intent intent = shortcut.intent;
2664
2665 // Check for special shortcuts
2666 if (intent.getComponent() != null) {
2667 final String shortcutClass = intent.getComponent().getClassName();
2668
2669 if (shortcutClass.equals(MemoryDumpActivity.class.getName())) {
2670 MemoryDumpActivity.startDump(this);
2671 return;
2672 } else if (shortcutClass.equals(ToggleWeightWatcher.class.getName())) {
2673 toggleShowWeightWatcher();
2674 return;
2675 }
2676 }
2677
2678 // Check for abandoned promise
2679 if ((v instanceof BubbleTextView)
2680 && shortcut.isPromise()
2681 && !shortcut.hasStatusFlag(ShortcutInfo.FLAG_INSTALL_SESSION_ACTIVE)) {
2682 showBrokenAppInstallDialog(
2683 shortcut.getTargetComponent().getPackageName(),
2684 new DialogInterface.OnClickListener() {
2685 public void onClick(DialogInterface dialog, int id) {
2686 startAppShortcutOrInfoActivity(v);
2687 }
2688 });
2689 return;
2690 }
2691
2692 // Start activities
2693 startAppShortcutOrInfoActivity(v);
2694
2695 if (mLauncherCallbacks != null) {
2696 mLauncherCallbacks.onClickAppShortcut(v);
2697 }
2698 }
2699
2700 @Thunk void startAppShortcutOrInfoActivity(View v) {
2701 Object tag = v.getTag();
2702 final ShortcutInfo shortcut;
2703 final Intent intent;
2704 if (tag instanceof ShortcutInfo) {
2705 shortcut = (ShortcutInfo) tag;
2706 intent = shortcut.intent;
2707 int[] pos = new int[2];
2708 v.getLocationOnScreen(pos);
2709 intent.setSourceBounds(new Rect(pos[0], pos[1],
2710 pos[0] + v.getWidth(), pos[1] + v.getHeight()));
2711
2712 } else if (tag instanceof AppInfo) {
2713 shortcut = null;
2714 intent = ((AppInfo) tag).intent;
2715 } else {
2716 throw new IllegalArgumentException("Input must be a Shortcut or AppInfo");
2717 }
2718
2719 boolean success = startActivitySafely(v, intent, tag);
2720 mStats.recordLaunch(intent, shortcut);
2721
2722 if (success && v instanceof BubbleTextView) {
2723 mWaitingForResume = (BubbleTextView) v;
2724 mWaitingForResume.setStayPressed(true);
2725 }
2726 }
2727
2728 /**
2729 * Event handler for a folder icon click.
2730 *
2731 * @param v The view that was clicked. Must be an instance of {@link FolderIcon}.
2732 */
2733 protected void onClickFolderIcon(View v) {
2734 if (LOGD) Log.d(TAG, "onClickFolder");
2735 if (!(v instanceof FolderIcon)){
2736 throw new IllegalArgumentException("Input must be a FolderIcon");
2737 }
2738
2739 FolderIcon folderIcon = (FolderIcon) v;
2740 final FolderInfo info = folderIcon.getFolderInfo();
2741 Folder openFolder = mWorkspace.getFolderForTag(info);
2742
2743 // If the folder info reports that the associated folder is open, then verify that
2744 // it is actually opened. There have been a few instances where this gets out of sync.
2745 if (info.opened && openFolder == null) {
2746 Log.d(TAG, "Folder info marked as open, but associated folder is not open. Screen: "
2747 + info.screenId + " (" + info.cellX + ", " + info.cellY + ")");
2748 info.opened = false;
2749 }
2750
2751 if (!info.opened && !folderIcon.getFolder().isDestroyed()) {
2752 // Close any open folder
2753 closeFolder();
2754 // Open the requested folder
2755 openFolder(folderIcon);
2756 } else {
2757 // Find the open folder...
2758 int folderScreen;
2759 if (openFolder != null) {
2760 folderScreen = mWorkspace.getPageForView(openFolder);
2761 // .. and close it
2762 closeFolder(openFolder);
2763 if (folderScreen != mWorkspace.getCurrentPage()) {
2764 // Close any folder open on the current screen
2765 closeFolder();
2766 // Pull the folder onto this screen
2767 openFolder(folderIcon);
2768 }
2769 }
2770 }
2771
2772 if (mLauncherCallbacks != null) {
2773 mLauncherCallbacks.onClickFolderIcon(v);
2774 }
2775 }
2776
2777 /**
2778 * Event handler for the (Add) Widgets button that appears after a long press
2779 * on the home screen.
2780 */
2781 protected void onClickAddWidgetButton(View view) {
2782 if (LOGD) Log.d(TAG, "onClickAddWidgetButton");
2783 if (mIsSafeModeEnabled) {
2784 Toast.makeText(this, R.string.safemode_widget_error, Toast.LENGTH_SHORT).show();
2785 } else {
2786 showWidgetsView(true /* animated */, true /* resetPageToZero */);
2787 if (mLauncherCallbacks != null) {
2788 mLauncherCallbacks.onClickAddWidgetButton(view);
2789 }
2790 }
2791 }
2792
2793 /**
2794 * Event handler for the wallpaper picker button that appears after a long press
2795 * on the home screen.
2796 */
2797 protected void onClickWallpaperPicker(View v) {
2798 if (LOGD) Log.d(TAG, "onClickWallpaperPicker");
2799 startActivityForResult(new Intent(Intent.ACTION_SET_WALLPAPER).setPackage(getPackageName()),
2800 REQUEST_PICK_WALLPAPER);
2801
2802 if (mLauncherCallbacks != null) {
2803 mLauncherCallbacks.onClickWallpaperPicker(v);
2804 }
2805 }
2806
2807 /**
2808 * Event handler for a click on the settings button that appears after a long press
2809 * on the home screen.
2810 */
2811 protected void onClickSettingsButton(View v) {
2812 if (LOGD) Log.d(TAG, "onClickSettingsButton");
2813 if (mLauncherCallbacks != null) {
2814 mLauncherCallbacks.onClickSettingsButton(v);
2815 }
2816 }
2817
2818 public void onTouchDownAllAppsButton(View v) {
2819 // Provide the same haptic feedback that the system offers for virtual keys.
2820 v.performHapticFeedback(HapticFeedbackConstants.VIRTUAL_KEY);
2821 }
2822
2823 public void performHapticFeedbackOnTouchDown(View v) {
2824 // Provide the same haptic feedback that the system offers for virtual keys.
2825 v.performHapticFeedback(HapticFeedbackConstants.VIRTUAL_KEY);
2826 }
2827
2828 public View.OnTouchListener getHapticFeedbackTouchListener() {
2829 if (mHapticFeedbackTouchListener == null) {
2830 mHapticFeedbackTouchListener = new View.OnTouchListener() {
2831 @Override
2832 public boolean onTouch(View v, MotionEvent event) {
2833 if ((event.getAction() & MotionEvent.ACTION_MASK) == MotionEvent.ACTION_DOWN) {
2834 v.performHapticFeedback(HapticFeedbackConstants.VIRTUAL_KEY);
2835 }
2836 return false;
2837 }
2838 };
2839 }
2840 return mHapticFeedbackTouchListener;
2841 }
2842
2843 public void onDragStarted(View view) {
2844 if (isOnCustomContent()) {
2845 // Custom content screen doesn't participate in drag and drop. If on custom
2846 // content screen, move to default.
2847 moveWorkspaceToDefaultScreen();
2848 }
2849
2850 if (mLauncherCallbacks != null) {
2851 mLauncherCallbacks.onDragStarted(view);
2852 }
2853 }
2854
2855 /**
2856 * Called when the user stops interacting with the launcher.
2857 * This implies that the user is now on the homescreen and is not doing housekeeping.
2858 */
2859 protected void onInteractionEnd() {
2860 if (mLauncherCallbacks != null) {
2861 mLauncherCallbacks.onInteractionEnd();
2862 }
2863 }
2864
2865 /**
2866 * Called when the user starts interacting with the launcher.
2867 * The possible interactions are:
2868 * - open all apps
2869 * - reorder an app shortcut, or a widget
2870 * - open the overview mode.
2871 * This is a good time to stop doing things that only make sense
2872 * when the user is on the homescreen and not doing housekeeping.
2873 */
2874 protected void onInteractionBegin() {
2875 if (mLauncherCallbacks != null) {
2876 mLauncherCallbacks.onInteractionBegin();
2877 }
2878 }
2879
2880 void startApplicationDetailsActivity(ComponentName componentName, UserHandleCompat user) {
2881 try {
2882 LauncherAppsCompat launcherApps = LauncherAppsCompat.getInstance(this);
2883 launcherApps.showAppDetailsForProfile(componentName, user);
2884 } catch (SecurityException e) {
2885 Toast.makeText(this, R.string.activity_not_found, Toast.LENGTH_SHORT).show();
2886 Log.e(TAG, "Launcher does not have permission to launch settings");
2887 } catch (ActivityNotFoundException e) {
2888 Toast.makeText(this, R.string.activity_not_found, Toast.LENGTH_SHORT).show();
2889 Log.e(TAG, "Unable to launch settings");
2890 }
2891 }
2892
2893 // returns true if the activity was started
2894 boolean startApplicationUninstallActivity(ComponentName componentName, int flags,
2895 UserHandleCompat user) {
2896 if ((flags & AppInfo.DOWNLOADED_FLAG) == 0) {
2897 // System applications cannot be installed. For now, show a toast explaining that.
2898 // We may give them the option of disabling apps this way.
2899 int messageId = R.string.uninstall_system_app_text;
2900 Toast.makeText(this, messageId, Toast.LENGTH_SHORT).show();
2901 return false;
2902 } else {
2903 String packageName = componentName.getPackageName();
2904 String className = componentName.getClassName();
2905 Intent intent = new Intent(
2906 Intent.ACTION_DELETE, Uri.fromParts("package", packageName, className));
2907 intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK |
2908 Intent.FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS);
2909 if (user != null) {
2910 user.addToIntent(intent, Intent.EXTRA_USER);
2911 }
2912 startActivity(intent);
2913 return true;
2914 }
2915 }
2916
2917 boolean startActivity(View v, Intent intent, Object tag) {
2918 intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
2919 try {
2920 // Only launch using the new animation if the shortcut has not opted out (this is a
2921 // private contract between launcher and may be ignored in the future).
2922 boolean useLaunchAnimation = (v != null) &&
2923 !intent.hasExtra(INTENT_EXTRA_IGNORE_LAUNCH_ANIMATION);
2924 LauncherAppsCompat launcherApps = LauncherAppsCompat.getInstance(this);
2925 UserManagerCompat userManager = UserManagerCompat.getInstance(this);
2926
2927 UserHandleCompat user = null;
2928 if (intent.hasExtra(AppInfo.EXTRA_PROFILE)) {
2929 long serialNumber = intent.getLongExtra(AppInfo.EXTRA_PROFILE, -1);
2930 user = userManager.getUserForSerialNumber(serialNumber);
2931 }
2932
2933 Bundle optsBundle = null;
2934 <<<<<<< MINE
2935 if (useLaunchAnimation) {
2936 ActivityOptions opts = null;
2937 if (sClipRevealMethod != null) {
2938 // TODO: call method directly when Launcher3 can depend on M APIs
2939 int left = 0, top = 0;
2940 int width = v.getMeasuredWidth(), height = v.getMeasuredHeight();
2941 if (v instanceof TextView) {
2942 // Launch from center of icon, not entire view
2943 Drawable icon = Workspace.getTextViewIcon((TextView) v);
2944 if (icon != null) {
2945 Rect bounds = icon.getBounds();
2946 left = (width - bounds.width()) / 2;
2947 top = v.getPaddingTop();
2948 width = bounds.width();
2949 height = bounds.height();
2950 }
2951 }
2952 try {
2953 opts = (ActivityOptions) sClipRevealMethod.invoke(null, v,
2954 left, top, width, height);
2955 } catch (IllegalAccessException e) {
2956 Log.d(TAG, "Could not call makeClipRevealAnimation: " + e);
2957 sClipRevealMethod = null;
2958 } catch (InvocationTargetException e) {
2959 Log.d(TAG, "Could not call makeClipRevealAnimation: " + e);
2960 sClipRevealMethod = null;
2961 }
2962 }
2963 if (opts == null && !Utilities.isLmpOrAbove()) {
2964 opts = ActivityOptions.makeScaleUpAnimation(v, 0, 0,
2965 v.getMeasuredWidth(), v.getMeasuredHeight());
2966 }
2967 optsBundle = opts != null ? opts.toBundle() : null;
2968 ||||||| BASE
2969 if (useLaunchAnimation) {
2970 ActivityOptions opts = Utilities.isLmpOrAbove() ?
2971 ActivityOptions.makeCustomAnimation(this, R.anim.task_open_enter, R.anim.no_anim)🔵
2972 ActivityOptions.makeScaleUpAnimation(v, 0, 0, v.getMeasuredWidth(), v.getMeasured🔵
2973 optsBundle = opts.toBundle();
2974 }
2975
2976 if (user == null || user.equals(UserHandleCompat.myUserHandle())) {
2977 // Could be launching some bookkeeping activity
2978 startActivity(intent, optsBundle);
2979 } else {
2980 // TODO Component can be null when shortcuts are supported for secondary user
2981 launcherApps.startActivityForProfile(intent.getComponent(), user,
2982 intent.getSourceBounds(), optsBundle);
2983 }
2984 return true;
2985 } catch (SecurityException e) {
2986 Toast.makeText(this, R.string.activity_not_found, Toast.LENGTH_SHORT).show();
2987 Log.e(TAG, "Launcher does not have the permission to launch " + intent +
2988 ". Make sure to create a MAIN intent-filter for the corresponding activity " +
2989 "or use the exported attribute for this activity. "
2990 + "tag="+ tag + " intent=" + intent, e);
2991 }
2992 return false;
2993 }
2994 =======
2995 if (useLaunchAnimation && !Utilities.isLmpOrAbove()) {
2996 // On pre-L devices, we use the scale up transition.
2997 // Otherwise we use system default.
2998 ActivityOptions opts =
2999 ActivityOptions.makeScaleUpAnimation(v, 0, 0, v.getMeasuredWidth(), v.getMeasured🔵
3000 optsBundle = opts.toBundle();
3001 >>>>>>> YOURS
3002 }
3003
3004 if (user == null || user.equals(UserHandleCompat.myUserHandle())) {
3005 // Could be launching some bookkeeping activity
3006 startActivity(intent, optsBundle);
3007 } else {
3008 // TODO Component can be null when shortcuts are supported for secondary user
3009 launcherApps.startActivityForProfile(intent.getComponent(), user,
3010 intent.getSourceBounds(), optsBundle);
3011 }
3012 return true;
3013 } catch (SecurityException e) {
3014 Toast.makeText(this, R.string.activity_not_found, Toast.LENGTH_SHORT).show();
3015 Log.e(TAG, "Launcher does not have the permission to launch " + intent +
3016 ". Make sure to create a MAIN intent-filter for the corresponding activity " +
3017 "or use the exported attribute for this activity. "
3018 + "tag="+ tag + " intent=" + intent, e);
3019 }
3020 return false;
3021 }
3022
3023 boolean startActivitySafely(View v, Intent intent, Object tag) {
3024 boolean success = false;
3025 if (mIsSafeModeEnabled && !Utilities.isSystemApp(this, intent)) {
3026 Toast.makeText(this, R.string.safemode_shortcut_error, Toast.LENGTH_SHORT).show();
3027 return false;
3028 }
3029 try {
3030 success = startActivity(v, intent, tag);
3031 } catch (ActivityNotFoundException e) {
3032 Toast.makeText(this, R.string.activity_not_found, Toast.LENGTH_SHORT).show();
3033 Log.e(TAG, "Unable to launch. tag=" + tag + " intent=" + intent, e);
3034 }
3035 return success;
3036 }
3037
3038 /**
3039 * This method draws the FolderIcon to an ImageView and then adds and positions that ImageView
3040 * in the DragLayer in the exact absolute location of the original FolderIcon.
3041 */
3042 private void copyFolderIconToImage(FolderIcon fi) {
3043 final int width = fi.getMeasuredWidth();
3044 final int height = fi.getMeasuredHeight();
3045
3046 // Lazy load ImageView, Bitmap and Canvas
3047 if (mFolderIconImageView == null) {
3048 mFolderIconImageView = new ImageView(this);
3049 }
3050 if (mFolderIconBitmap == null || mFolderIconBitmap.getWidth() != width ||
3051 mFolderIconBitmap.getHeight() != height) {
3052 mFolderIconBitmap = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888);
3053 mFolderIconCanvas = new Canvas(mFolderIconBitmap);
3054 }
3055
3056 DragLayer.LayoutParams lp;
3057 if (mFolderIconImageView.getLayoutParams() instanceof DragLayer.LayoutParams) {
3058 lp = (DragLayer.LayoutParams) mFolderIconImageView.getLayoutParams();
3059 } else {
3060 lp = new DragLayer.LayoutParams(width, height);
3061 }
3062
3063 // The layout from which the folder is being opened may be scaled, adjust the starting
3064 // view size by this scale factor.
3065 float scale = mDragLayer.getDescendantRectRelativeToSelf(fi, mRectForFolderAnimation);
3066 lp.customPosition = true;
3067 lp.x = mRectForFolderAnimation.left;
3068 lp.y = mRectForFolderAnimation.top;
3069 lp.width = (int) (scale * width);
3070 lp.height = (int) (scale * height);
3071
3072 mFolderIconCanvas.drawColor(0, PorterDuff.Mode.CLEAR);
3073 fi.draw(mFolderIconCanvas);
3074 mFolderIconImageView.setImageBitmap(mFolderIconBitmap);
3075 if (fi.getFolder() != null) {
3076 mFolderIconImageView.setPivotX(fi.getFolder().getPivotXForIconAnimation());
3077 mFolderIconImageView.setPivotY(fi.getFolder().getPivotYForIconAnimation());
3078 }
3079 // Just in case this image view is still in the drag layer from a previous animation,
3080 // we remove it and re-add it.
3081 if (mDragLayer.indexOfChild(mFolderIconImageView) != -1) {
3082 mDragLayer.removeView(mFolderIconImageView);
3083 }
3084 mDragLayer.addView(mFolderIconImageView, lp);
3085 if (fi.getFolder() != null) {
3086 fi.getFolder().bringToFront();
3087 }
3088 }
3089
3090 private void growAndFadeOutFolderIcon(FolderIcon fi) {
3091 if (fi == null) return;
3092 PropertyValuesHolder alpha = PropertyValuesHolder.ofFloat("alpha", 0);
3093 PropertyValuesHolder scaleX = PropertyValuesHolder.ofFloat("scaleX", 1.5f);
3094 PropertyValuesHolder scaleY = PropertyValuesHolder.ofFloat("scaleY", 1.5f);
3095
3096 FolderInfo info = (FolderInfo) fi.getTag();
3097 if (info.container == LauncherSettings.Favorites.CONTAINER_HOTSEAT) {
3098 CellLayout cl = (CellLayout) fi.getParent().getParent();
3099 CellLayout.LayoutParams lp = (CellLayout.LayoutParams) fi.getLayoutParams();
3100 cl.setFolderLeaveBehindCell(lp.cellX, lp.cellY);
3101 }
3102
3103 // Push an ImageView copy of the FolderIcon into the DragLayer and hide the original
3104 copyFolderIconToImage(fi);
3105 fi.setVisibility(View.INVISIBLE);
3106
3107 ObjectAnimator oa = LauncherAnimUtils.ofPropertyValuesHolder(mFolderIconImageView, alpha,
3108 scaleX, scaleY);
3109 if (Utilities.isLmpOrAbove()) {
3110 oa.setInterpolator(new LogDecelerateInterpolator(100, 0));
3111 }
3112 oa.setDuration(getResources().getInteger(R.integer.config_folderExpandDuration));
3113 oa.start();
3114 }
3115
3116 private void shrinkAndFadeInFolderIcon(final FolderIcon fi) {
3117 if (fi == null) return;
3118 PropertyValuesHolder alpha = PropertyValuesHolder.ofFloat("alpha", 1.0f);
3119 PropertyValuesHolder scaleX = PropertyValuesHolder.ofFloat("scaleX", 1.0f);
3120 PropertyValuesHolder scaleY = PropertyValuesHolder.ofFloat("scaleY", 1.0f);
3121
3122 final CellLayout cl = (CellLayout) fi.getParent().getParent();
3123
3124 // We remove and re-draw the FolderIcon in-case it has changed
3125 mDragLayer.removeView(mFolderIconImageView);
3126 copyFolderIconToImage(fi);
3127 ObjectAnimator oa = LauncherAnimUtils.ofPropertyValuesHolder(mFolderIconImageView, alpha,
3128 scaleX, scaleY);
3129 oa.setDuration(getResources().getInteger(R.integer.config_folderExpandDuration));
3130 oa.addListener(new AnimatorListenerAdapter() {
3131 @Override
3132 public void onAnimationEnd(Animator animation) {
3133 if (cl != null) {
3134 cl.clearFolderLeaveBehind();
3135 // Remove the ImageView copy of the FolderIcon and make the original visible.
3136 mDragLayer.removeView(mFolderIconImageView);
3137 fi.setVisibility(View.VISIBLE);
3138 }
3139 }
3140 });
3141 oa.start();
3142 }
3143
3144 /**
3145 * Opens the user folder described by the specified tag. The opening of the folder
3146 * is animated relative to the specified View. If the View is null, no animation
3147 * is played.
3148 *
3149 * @param folderInfo The FolderInfo describing the folder to open.
3150 */
3151 public void openFolder(FolderIcon folderIcon) {
3152 Folder folder = folderIcon.getFolder();
3153 Folder openFolder = mWorkspace != null ? mWorkspace.getOpenFolder() : null;
3154 if (openFolder != null && openFolder != folder) {
3155 // Close any open folder before opening a folder.
3156 closeFolder();
3157 }
3158
3159 FolderInfo info = folder.mInfo;
3160
3161 info.opened = true;
3162
3163 // While the folder is open, the position of the icon cannot change.
3164 ((CellLayout.LayoutParams) folderIcon.getLayoutParams()).canReorder = false;
3165
3166 // Just verify that the folder hasn't already been added to the DragLayer.
3167 // There was a one-off crash where the folder had a parent already.
3168 if (folder.getParent() == null) {
3169 mDragLayer.addView(folder);
3170 mDragController.addDropTarget((DropTarget) folder);
3171 } else {
3172 Log.w(TAG, "Opening folder (" + folder + ") which already has a parent (" +
3173 folder.getParent() + ").");
3174 }
3175 folder.animateOpen();
3176 growAndFadeOutFolderIcon(folderIcon);
3177
3178 // Notify the accessibility manager that this folder "window" has appeared and occluded
3179 // the workspace items
3180 folder.sendAccessibilityEvent(AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED);
3181 getDragLayer().sendAccessibilityEvent(AccessibilityEvent.TYPE_WINDOW_CONTENT_CHANGED);
3182 }
3183
3184 public void closeFolder() {
3185 Folder folder = mWorkspace != null ? mWorkspace.getOpenFolder() : null;
3186 if (folder != null) {
3187 if (folder.isEditingName()) {
3188 folder.dismissEditingName();
3189 }
3190 closeFolder(folder);
3191 }
3192 }
3193
3194 void closeFolder(Folder folder) {
3195 folder.getInfo().opened = false;
3196
3197 ViewGroup parent = (ViewGroup) folder.getParent().getParent();
3198 if (parent != null) {
3199 FolderIcon fi = (FolderIcon) mWorkspace.getViewForTag(folder.mInfo);
3200 shrinkAndFadeInFolderIcon(fi);
3201 if (fi != null) {
3202 ((CellLayout.LayoutParams) fi.getLayoutParams()).canReorder = true;
3203 }
3204 }
3205 folder.animateClosed();
3206
3207 // Notify the accessibility manager that this folder "window" has disappeard and no
3208 // longer occludeds the workspace items
3209 getDragLayer().sendAccessibilityEvent(AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED);
3210 }
3211
3212 public boolean onLongClick(View v) {
3213 if (!isDraggingEnabled()) return false;
3214 if (isWorkspaceLocked()) return false;
3215 if (mState != State.WORKSPACE) return false;
3216
3217 if (v instanceof Workspace) {
3218 if (!mWorkspace.isInOverviewMode()) {
3219 if (mWorkspace.enterOverviewMode()) {
3220 mWorkspace.performHapticFeedback(HapticFeedbackConstants.LONG_PRESS,
3221 HapticFeedbackConstants.FLAG_IGNORE_VIEW_SETTING);
3222 return true;
3223 } else {
3224 return false;
3225 }
3226 } else {
3227 return false;
3228 }
3229 }
3230
3231 CellLayout.CellInfo longClickCellInfo = null;
3232 View itemUnderLongClick = null;
3233 if (v.getTag() instanceof ItemInfo) {
3234 ItemInfo info = (ItemInfo) v.getTag();
3235 longClickCellInfo = new CellLayout.CellInfo(v, info);
3236 itemUnderLongClick = longClickCellInfo.cell;
3237 resetAddInfo();
3238 }
3239
3240 // The hotseat touch handling does not go through Workspace, and we always allow long press
3241 // on hotseat items.
3242 final boolean inHotseat = isHotseatLayout(v);
3243 boolean allowLongPress = inHotseat || mWorkspace.allowLongPress();
3244 if (allowLongPress && !mDragController.isDragging()) {
3245 if (itemUnderLongClick == null) {
3246 // User long pressed on empty space
3247 mWorkspace.performHapticFeedback(HapticFeedbackConstants.LONG_PRESS,
3248 HapticFeedbackConstants.FLAG_IGNORE_VIEW_SETTING);
3249 if (mWorkspace.isInOverviewMode()) {
3250 mWorkspace.startReordering(v);
3251 } else {
3252 mWorkspace.enterOverviewMode();
3253 }
3254 } else {
3255 final boolean isAllAppsButton = inHotseat && isAllAppsButtonRank(
3256 mHotseat.getOrderInHotseat(
3257 longClickCellInfo.cellX,
3258 longClickCellInfo.cellY));
3259 if (!(itemUnderLongClick instanceof Folder || isAllAppsButton)) {
3260 // User long pressed on an item
3261 mWorkspace.startDrag(longClickCellInfo);
3262 }
3263 }
3264 }
3265 return true;
3266 }
3267
3268 boolean isHotseatLayout(View layout) {
3269 return mHotseat != null && layout != null &&
3270 (layout instanceof CellLayout) && (layout == mHotseat.getLayout());
3271 }
3272
3273 /**
3274 * Returns the CellLayout of the specified container at the specified screen.
3275 */
3276 public CellLayout getCellLayout(long container, long screenId) {
3277 if (container == LauncherSettings.Favorites.CONTAINER_HOTSEAT) {
3278 if (mHotseat != null) {
3279 return mHotseat.getLayout();
3280 } else {
3281 return null;
3282 }
3283 } else {
3284 return mWorkspace.getScreenWithId(screenId);
3285 }
3286 }
3287
3288 /**
3289 * For overridden classes.
3290 */
3291 public boolean isAllAppsVisible() {
3292 return isAppsViewVisible();
3293 }
3294
3295 public boolean isAppsViewVisible() {
3296 return (mState == State.APPS) || (mOnResumeState == State.APPS);
3297 }
3298
3299 public boolean isWidgetsViewVisible() {
3300 return (mState == State.WIDGETS) || (mOnResumeState == State.WIDGETS);
3301 }
3302
3303 private void setWorkspaceBackground(boolean workspace) {
3304 mLauncherView.setBackground(workspace ?
3305 mWorkspaceBackgroundDrawable : null);
3306 }
3307
3308 protected void changeWallpaperVisiblity(boolean visible) {
3309 int wpflags = visible ? WindowManager.LayoutParams.FLAG_SHOW_WALLPAPER : 0;
3310 int curflags = getWindow().getAttributes().flags
3311 & WindowManager.LayoutParams.FLAG_SHOW_WALLPAPER;
3312 if (wpflags != curflags) {
3313 getWindow().setFlags(wpflags, WindowManager.LayoutParams.FLAG_SHOW_WALLPAPER);
3314 }
3315 setWorkspaceBackground(visible);
3316 }
3317
3318 @Override
3319 public void onTrimMemory(int level) {
3320 super.onTrimMemory(level);
3321 if (level >= ComponentCallbacks2.TRIM_MEMORY_UI_HIDDEN) {
3322 // The widget preview db can result in holding onto over
3323 // 3MB of memory for caching which isn't necessary.
3324 SQLiteDatabase.releaseMemory();
3325
3326 // This clears all widget bitmaps from the widget tray
3327 // TODO(hyunyoungs)
3328 }
3329 if (mLauncherCallbacks != null) {
3330 mLauncherCallbacks.onTrimMemory(level);
3331 }
3332 }
3333
3334 @Override
3335 public void onStateTransitionHideSearchBar() {
3336 // Hide the search bar
3337 if (mSearchDropTargetBar != null) {
3338 mSearchDropTargetBar.hideSearchBar(false /* animated */);
3339 }
3340 }
3341
3342 protected void showWorkspace(boolean animated) {
3343 showWorkspace(animated, null);
3344 }
3345
3346 void showWorkspace(boolean animated, Runnable onCompleteRunnable) {
3347 boolean changed = mState != State.WORKSPACE ||
3348 mWorkspace.getState() != Workspace.State.NORMAL;
3349 if (changed) {
3350 boolean wasInSpringLoadedMode = (mState != State.WORKSPACE);
3351 mWorkspace.setVisibility(View.VISIBLE);
3352 mStateTransitionAnimation.startAnimationToWorkspace(mState, Workspace.State.NORMAL,
3353 animated, onCompleteRunnable);
3354
3355 // Show the search bar (only animate if we were showing the drop target bar in spring
3356 // loaded mode)
3357 if (mSearchDropTargetBar != null) {
3358 mSearchDropTargetBar.showSearchBar(animated && wasInSpringLoadedMode);
3359 }
3360
3361 // Set focus to the AppsCustomize button
3362 if (mAllAppsButton != null) {
3363 mAllAppsButton.requestFocus();
3364 }
3365 }
3366
3367 // Change the state *after* we've called all the transition code
3368 mState = State.WORKSPACE;
3369
3370 // Resume the auto-advance of widgets
3371 mUserPresent = true;
3372 updateAutoAdvanceState();
3373
3374 if (changed) {
3375 // Send an accessibility event to announce the context change
3376 getWindow().getDecorView()
3377 .sendAccessibilityEvent(AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED);
3378
3379 onWorkspaceShown(animated);
3380 }
3381 }
3382
3383 void showOverviewMode(boolean animated) {
3384 mWorkspace.setVisibility(View.VISIBLE);
3385 mStateTransitionAnimation.startAnimationToWorkspace(mState, Workspace.State.OVERVIEW,
3386 animated, null /* onCompleteRunnable */);
3387 mState = State.WORKSPACE;
3388 onWorkspaceShown(animated);
3389 }
3390
3391 public void onWorkspaceShown(boolean animated) {
3392 }
3393
3394 /**
3395 * Shows the apps view.
3396 */
3397 void showAppsView(boolean animated, boolean resetListToTop) {
3398 if (resetListToTop) {
3399 mAppsView.scrollToTop();
3400 }
3401 showAppsOrWidgets(animated, State.APPS);
3402 }
3403
3404 /**
3405 * Shows the widgets view.
3406 */
3407 void showWidgetsView(boolean animated, boolean resetPageToZero) {
3408 Log.d(TAG, "showWidgetsView:" + animated + " resetPageToZero:" + resetPageToZero);
3409 if (resetPageToZero) {
3410 mWidgetsView.scrollToTop();
3411 }
3412 showAppsOrWidgets(animated, State.WIDGETS);
3413
3414 mWidgetsView.post(new Runnable() {
3415 @Override
3416 public void run() {
3417 mWidgetsView.requestFocus();
3418 }
3419 });
3420 }
3421
3422 /**
3423 * Sets up the transition to show the apps/widgets view.
3424 */
3425 private void showAppsOrWidgets(boolean animated, State toState) {
3426 if (mState != State.WORKSPACE) return;
3427 if (toState != State.APPS && toState != State.WIDGETS) return;
3428
3429 if (toState == State.APPS) {
3430 mStateTransitionAnimation.startAnimationToAllApps(animated);
3431 if (mLauncherCallbacks != null) {
3432 mLauncherCallbacks.onAllAppsShown();
3433 }
3434 } else {
3435 mStateTransitionAnimation.startAnimationToWidgets(animated);
3436 }
3437
3438 // Change the state *after* we've called all the transition code
3439 mState = toState;
3440
3441 // Pause the auto-advance of widgets until we are out of AllApps
3442 mUserPresent = false;
3443 updateAutoAdvanceState();
3444 closeFolder();
3445
3446 // Send an accessibility event to announce the context change
3447 getWindow().getDecorView()
3448 .sendAccessibilityEvent(AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED);
3449 }
3450
3451 public void enterSpringLoadedDragMode() {
3452 Log.d(TAG, String.format("enterSpringLoadedDragMode [mState=%s",
3453 mState.name()));
3454 if (mState == State.WORKSPACE || mState == State.APPS_SPRING_LOADED ||
3455 mState == State.WIDGETS_SPRING_LOADED) {
3456 return;
3457 }
3458
3459 mStateTransitionAnimation.startAnimationToWorkspace(mState, Workspace.State.SPRING_LOADED,
3460 true /* animated */, null /* onCompleteRunnable */);
3461 mState = isAppsViewVisible() ? State.APPS_SPRING_LOADED : State.WIDGETS_SPRING_LOADED;
3462 }
3463
3464 public void exitSpringLoadedDragModeDelayed(final boolean successfulDrop, int delay,
3465 final Runnable onCompleteRunnable) {
3466 if (mState != State.APPS_SPRING_LOADED && mState != State.WIDGETS_SPRING_LOADED) return;
3467
3468 mHandler.postDelayed(new Runnable() {
3469 @Override
3470 public void run() {
3471 if (successfulDrop) {
3472 // TODO(hyunyoungs): verify if this hack is still needed, if not, delete.
3473 //
3474 // Before we show workspace, hide all apps again because
3475 // exitSpringLoadedDragMode made it visible. This is a bit hacky; we should
3476 // clean up our state transition functions
3477 mWidgetsView.setVisibility(View.GONE);
3478 showWorkspace(true, onCompleteRunnable);
3479 } else {
3480 exitSpringLoadedDragMode();
3481 }
3482 }
3483 }, delay);
3484 }
3485
3486 void exitSpringLoadedDragMode() {
3487 if (mState == State.APPS_SPRING_LOADED) {
3488 mStateTransitionAnimation.startAnimationToAllApps(true /* animated */);
3489 mState = State.APPS;
3490 } else if (mState == State.WIDGETS_SPRING_LOADED) {
3491 mStateTransitionAnimation.startAnimationToWidgets(true /* animated */);
3492 mState = State.WIDGETS;
3493 }
3494 // Otherwise, we are not in spring loaded mode, so don't do anything.
3495 }
3496
3497 void lockAllApps() {
3498 // TODO
3499 }
3500
3501 void unlockAllApps() {
3502 // TODO
3503 }
3504
3505 protected void disableVoiceButtonProxy(boolean disable) {
3506 // NO-OP
3507 }
3508
3509 public View getOrCreateQsbBar() {
3510 if (mLauncherCallbacks != null && mLauncherCallbacks.providesSearch()) {
3511 return mLauncherCallbacks.getQsbBar();
3512 }
3513
3514 if (mQsb == null) {
3515 AppWidgetProviderInfo searchProvider = Utilities.getSearchWidgetProvider(this);
3516 if (searchProvider == null) {
3517 return null;
3518 }
3519
3520 Bundle opts = new Bundle();
3521 opts.putInt(AppWidgetManager.OPTION_APPWIDGET_HOST_CATEGORY,
3522 AppWidgetProviderInfo.WIDGET_CATEGORY_SEARCHBOX);
3523
3524 SharedPreferences sp = getSharedPreferences(
3525 LauncherAppState.getSharedPreferencesKey(), MODE_PRIVATE);
3526 int widgetId = sp.getInt(QSB_WIDGET_ID, -1);
3527 AppWidgetProviderInfo widgetInfo = mAppWidgetManager.getAppWidgetInfo(widgetId);
3528 if (!searchProvider.provider.flattenToString().equals(
3529 sp.getString(QSB_WIDGET_PROVIDER, null))
3530 || (widgetInfo == null)
3531 || !widgetInfo.provider.equals(searchProvider.provider)) {
3532 // A valid widget is not already bound.
3533 if (widgetId > -1) {
3534 mAppWidgetHost.deleteAppWidgetId(widgetId);
3535 widgetId = -1;
3536 }
3537
3538 // Try to bind a new widget
3539 widgetId = mAppWidgetHost.allocateAppWidgetId();
3540
3541 if (!AppWidgetManagerCompat.getInstance(this)
3542 .bindAppWidgetIdIfAllowed(widgetId, searchProvider, opts)) {
3543 mAppWidgetHost.deleteAppWidgetId(widgetId);
3544 widgetId = -1;
3545 }
3546
3547 sp.edit()
3548 .putInt(QSB_WIDGET_ID, widgetId)
3549 .putString(QSB_WIDGET_PROVIDER, searchProvider.provider.flattenToString())
3550 .commit();
3551 }
3552
3553 if (widgetId != -1) {
3554 mQsb = mAppWidgetHost.createView(this, widgetId, searchProvider);
3555 mQsb.updateAppWidgetOptions(opts);
3556 mQsb.setPadding(0, 0, 0, 0);
3557 mSearchDropTargetBar.addView(mQsb);
3558 mSearchDropTargetBar.setQsbSearchBar(mQsb);
3559 }
3560 }
3561 return mQsb;
3562 }
3563
3564 private void reinflateQSBIfNecessary() {
3565 if (mQsb instanceof LauncherAppWidgetHostView &&
3566 ((LauncherAppWidgetHostView) mQsb).isReinflateRequired()) {
3567 mSearchDropTargetBar.removeView(mQsb);
3568 mQsb = null;
3569 mSearchDropTargetBar.setQsbSearchBar(getOrCreateQsbBar());
3570 }
3571 }
3572
3573 @Override
3574 public boolean dispatchPopulateAccessibilityEvent(AccessibilityEvent event) {
3575 final boolean result = super.dispatchPopulateAccessibilityEvent(event);
3576 final List<CharSequence> text = event.getText();
3577 text.clear();
3578 // Populate event with a fake title based on the current state.
3579 if (mState == State.APPS) {
3580 text.add("Apps");
3581 } else if (mState == State.WIDGETS) {
3582 text.add("Widgets");
3583 } else {
3584 text.add(getString(R.string.all_apps_home_button_label));
3585 }
3586 return result;
3587 }
3588
3589 /**
3590 * Receives notifications when system dialogs are to be closed.
3591 */
3592 @Thunk class CloseSystemDialogsIntentReceiver extends BroadcastReceiver {
3593 @Override
3594 public void onReceive(Context context, Intent intent) {
3595 closeSystemDialogs();
3596 }
3597 }
3598
3599 /**
3600 * Receives notifications whenever the appwidgets are reset.
3601 */
3602 private class AppWidgetResetObserver extends ContentObserver {
3603 public AppWidgetResetObserver() {
3604 super(new Handler());
3605 }
3606
3607 @Override
3608 public void onChange(boolean selfChange) {
3609 onAppWidgetReset();
3610 }
3611 }
3612
3613 /**
3614 * If the activity is currently paused, signal that we need to run the passed Runnable
3615 * in onResume.
3616 *
3617 * This needs to be called from incoming places where resources might have been loaded
3618 * while we are paused. That is becaues the Configuration might be wrong
3619 * when we're not running, and if it comes back to what it was when we
3620 * were paused, we are not restarted.
3621 *
3622 * Implementation of the method from LauncherModel.Callbacks.
3623 *
3624 * @return true if we are currently paused. The caller might be able to
3625 * skip some work in that case since we will come back again.
3626 */
3627 private boolean waitUntilResume(Runnable run, boolean deletePreviousRunnables) {
3628 if (mPaused) {
3629 Log.i(TAG, "Deferring update until onResume");
3630 if (deletePreviousRunnables) {
3631 while (mBindOnResumeCallbacks.remove(run)) {
3632 }
3633 }
3634 mBindOnResumeCallbacks.add(run);
3635 return true;
3636 } else {
3637 return false;
3638 }
3639 }
3640
3641 private boolean waitUntilResume(Runnable run) {
3642 return waitUntilResume(run, false);
3643 }
3644
3645 public void addOnResumeCallback(Runnable run) {
3646 mOnResumeCallbacks.add(run);
3647 }
3648
3649 /**
3650 * If the activity is currently paused, signal that we need to re-run the loader
3651 * in onResume.
3652 *
3653 * This needs to be called from incoming places where resources might have been loaded
3654 * while we are paused. That is becaues the Configuration might be wrong
3655 * when we're not running, and if it comes back to what it was when we
3656 * were paused, we are not restarted.
3657 *
3658 * Implementation of the method from LauncherModel.Callbacks.
3659 *
3660 * @return true if we are currently paused. The caller might be able to
3661 * skip some work in that case since we will come back again.
3662 */
3663 public boolean setLoadOnResume() {
3664 if (mPaused) {
3665 Log.i(TAG, "setLoadOnResume");
3666 mOnResumeNeedsLoad = true;
3667 return true;
3668 } else {
3669 return false;
3670 }
3671 }
3672
3673 /**
3674 * Implementation of the method from LauncherModel.Callbacks.
3675 */
3676 public int getCurrentWorkspaceScreen() {
3677 if (mWorkspace != null) {
3678 return mWorkspace.getCurrentPage();
3679 } else {
3680 return SCREEN_COUNT / 2;
3681 }
3682 }
3683
3684 /**
3685 * Refreshes the shortcuts shown on the workspace.
3686 *
3687 * Implementation of the method from LauncherModel.Callbacks.
3688 */
3689 public void startBinding() {
3690 setWorkspaceLoading(true);
3691
3692 // If we're starting binding all over again, clear any bind calls we'd postponed in
3693 // the past (see waitUntilResume) -- we don't need them since we're starting binding
3694 // from scratch again
3695 mBindOnResumeCallbacks.clear();
3696
3697 // Clear the workspace because it's going to be rebound
3698 mWorkspace.clearDropTargets();
3699 mWorkspace.removeAllWorkspaceScreens();
3700
3701 mWidgetsToAdvance.clear();
3702 if (mHotseat != null) {
3703 mHotseat.resetLayout();
3704 }
3705 }
3706
3707 @Override
3708 public void bindScreens(ArrayList<Long> orderedScreenIds) {
3709 bindAddScreens(orderedScreenIds);
3710
3711 // If there are no screens, we need to have an empty screen
3712 if (orderedScreenIds.size() == 0) {
3713 mWorkspace.addExtraEmptyScreen();
3714 }
3715
3716 // Create the custom content page (this call updates mDefaultScreen which calls
3717 // setCurrentPage() so ensure that all pages are added before calling this).
3718 if (hasCustomContentToLeft()) {
3719 mWorkspace.createCustomContentContainer();
3720 populateCustomContentContainer();
3721 }
3722 }
3723
3724 @Override
3725 public void bindAddScreens(ArrayList<Long> orderedScreenIds) {
3726 // Log to disk
3727 Launcher.addDumpLog(TAG, "11683562 - bindAddScreens()", true);
3728 Launcher.addDumpLog(TAG, "11683562 - orderedScreenIds: " +
3729 TextUtils.join(", ", orderedScreenIds), true);
3730 int count = orderedScreenIds.size();
3731 for (int i = 0; i < count; i++) {
3732 mWorkspace.insertNewWorkspaceScreenBeforeEmptyScreen(orderedScreenIds.get(i));
3733 }
3734 }
3735
3736 @Override
3737 public void bindAddPendingItem(final PendingAddItemInfo info, final long container,
3738 final long screenId, final int[] cell, final int spanX, final int spanY) {
3739 showWorkspace(true, new Runnable() {
3740
3741 @Override
3742 public void run() {
3743 mWorkspace.snapToPage(mWorkspace.getPageIndexForScreenId(screenId));
3744 addPendingItem(info, container, screenId, cell, spanX, spanY);
3745 }
3746 });
3747 }
3748
3749 private boolean shouldShowWeightWatcher() {
3750 String spKey = LauncherAppState.getSharedPreferencesKey();
3751 SharedPreferences sp = getSharedPreferences(spKey, Context.MODE_PRIVATE);
3752 boolean show = sp.getBoolean(SHOW_WEIGHT_WATCHER, SHOW_WEIGHT_WATCHER_DEFAULT);
3753
3754 return show;
3755 }
3756
3757 private void toggleShowWeightWatcher() {
3758 String spKey = LauncherAppState.getSharedPreferencesKey();
3759 SharedPreferences sp = getSharedPreferences(spKey, Context.MODE_PRIVATE);
3760 boolean show = sp.getBoolean(SHOW_WEIGHT_WATCHER, true);
3761
3762 show = !show;
3763
3764 SharedPreferences.Editor editor = sp.edit();
3765 editor.putBoolean(SHOW_WEIGHT_WATCHER, show);
3766 editor.commit();
3767
3768 if (mWeightWatcher != null) {
3769 mWeightWatcher.setVisibility(show ? View.VISIBLE : View.GONE);
3770 }
3771 }
3772
3773 public void bindAppsAdded(final ArrayList<Long> newScreens,
3774 final ArrayList<ItemInfo> addNotAnimated,
3775 final ArrayList<ItemInfo> addAnimated,
3776 final ArrayList<AppInfo> addedApps) {
3777 Runnable r = new Runnable() {
3778 public void run() {
3779 bindAppsAdded(newScreens, addNotAnimated, addAnimated, addedApps);
3780 }
3781 };
3782 if (waitUntilResume(r)) {
3783 return;
3784 }
3785
3786 // Add the new screens
3787 if (newScreens != null) {
3788 bindAddScreens(newScreens);
3789 }
3790
3791 // We add the items without animation on non-visible pages, and with
3792 // animations on the new page (which we will try and snap to).
3793 if (addNotAnimated != null && !addNotAnimated.isEmpty()) {
3794 bindItems(addNotAnimated, 0,
3795 addNotAnimated.size(), false);
3796 }
3797 if (addAnimated != null && !addAnimated.isEmpty()) {
3798 bindItems(addAnimated, 0,
3799 addAnimated.size(), true);
3800 }
3801
3802 // Remove the extra empty screen
3803 mWorkspace.removeExtraEmptyScreen(false, false);
3804
3805 if (addedApps != null && mAppsView != null) {
3806 mAppsView.addApps(addedApps);
3807 }
3808 }
3809
3810 /**
3811 * Bind the items start-end from the list.
3812 *
3813 * Implementation of the method from LauncherModel.Callbacks.
3814 */
3815 public void bindItems(final ArrayList<ItemInfo> shortcuts, final int start, final int end,
3816 final boolean forceAnimateIcons) {
3817 Runnable r = new Runnable() {
3818 public void run() {
3819 bindItems(shortcuts, start, end, forceAnimateIcons);
3820 }
3821 };
3822 if (waitUntilResume(r)) {
3823 return;
3824 }
3825
3826 // Get the list of added shortcuts and intersect them with the set of shortcuts here
3827 final AnimatorSet anim = LauncherAnimUtils.createAnimatorSet();
3828 final Collection<Animator> bounceAnims = new ArrayList<Animator>();
3829 final boolean animateIcons = forceAnimateIcons && canRunNewAppsAnimation();
3830 Workspace workspace = mWorkspace;
3831 long newShortcutsScreenId = -1;
3832 for (int i = start; i < end; i++) {
3833 final ItemInfo item = shortcuts.get(i);
3834
3835 // Short circuit if we are loading dock items for a configuration which has no dock
3836 if (item.container == LauncherSettings.Favorites.CONTAINER_HOTSEAT &&
3837 mHotseat == null) {
3838 continue;
3839 }
3840
3841 switch (item.itemType) {
3842 case LauncherSettings.Favorites.ITEM_TYPE_APPLICATION:
3843 case LauncherSettings.Favorites.ITEM_TYPE_SHORTCUT:
3844 ShortcutInfo info = (ShortcutInfo) item;
3845 View shortcut = createShortcut(info);
3846
3847 /*
3848 * TODO: FIX collision case
3849 */
3850 if (item.container == LauncherSettings.Favorites.CONTAINER_DESKTOP) {
3851 CellLayout cl = mWorkspace.getScreenWithId(item.screenId);
3852 if (cl != null && cl.isOccupied(item.cellX, item.cellY)) {
3853 View v = cl.getChildAt(item.cellX, item.cellY);
3854 Object tag = v.getTag();
3855 String desc = "Collision while binding workspace item: " + item
3856 + ". Collides with " + tag;
3857 if (LauncherAppState.isDogfoodBuild()) {
3858 throw (new RuntimeException(desc));
3859 } else {
3860 Log.d(TAG, desc);
3861 }
3862 }
3863 }
3864
3865 workspace.addInScreenFromBind(shortcut, item.container, item.screenId, item.cellX,
3866 item.cellY, 1, 1);
3867 if (animateIcons) {
3868 // Animate all the applications up now
3869 shortcut.setAlpha(0f);
3870 shortcut.setScaleX(0f);
3871 shortcut.setScaleY(0f);
3872 bounceAnims.add(createNewAppBounceAnimation(shortcut, i));
3873 newShortcutsScreenId = item.screenId;
3874 }
3875 break;
3876 case LauncherSettings.Favorites.ITEM_TYPE_FOLDER:
3877 FolderIcon newFolder = FolderIcon.fromXml(R.layout.folder_icon, this,
3878 (ViewGroup) workspace.getChildAt(workspace.getCurrentPage()),
3879 (FolderInfo) item, mIconCache);
3880 workspace.addInScreenFromBind(newFolder, item.container, item.screenId, item.cellX,
3881 item.cellY, 1, 1);
3882 break;
3883 default:
3884 throw new RuntimeException("Invalid Item Type");
3885 }
3886 }
3887
3888 if (animateIcons) {
3889 // Animate to the correct page
3890 if (newShortcutsScreenId > -1) {
3891 long currentScreenId = mWorkspace.getScreenIdForPageIndex(mWorkspace.getNextPage());
3892 final int newScreenIndex = mWorkspace.getPageIndexForScreenId(newShortcutsScreenId);
3893 final Runnable startBounceAnimRunnable = new Runnable() {
3894 public void run() {
3895 anim.playTogether(bounceAnims);
3896 anim.start();
3897 }
3898 };
3899 if (newShortcutsScreenId != currentScreenId) {
3900 // We post the animation slightly delayed to prevent slowdowns
3901 // when we are loading right after we return to launcher.
3902 mWorkspace.postDelayed(new Runnable() {
3903 public void run() {
3904 if (mWorkspace != null) {
3905 mWorkspace.snapToPage(newScreenIndex);
3906 mWorkspace.postDelayed(startBounceAnimRunnable,
3907 NEW_APPS_ANIMATION_DELAY);
3908 }
3909 }
3910 }, NEW_APPS_PAGE_MOVE_DELAY);
3911 } else {
3912 mWorkspace.postDelayed(startBounceAnimRunnable, NEW_APPS_ANIMATION_DELAY);
3913 }
3914 }
3915 }
3916 workspace.requestLayout();
3917 }
3918
3919 /**
3920 * Implementation of the method from LauncherModel.Callbacks.
3921 */
3922 public void bindFolders(final HashMap<Long, FolderInfo> folders) {
3923 Runnable r = new Runnable() {
3924 public void run() {
3925 bindFolders(folders);
3926 }
3927 };
3928 if (waitUntilResume(r)) {
3929 return;
3930 }
3931 sFolders.clear();
3932 sFolders.putAll(folders);
3933 }
3934
3935 /**
3936 * Add the views for a widget to the workspace.
3937 *
3938 * Implementation of the method from LauncherModel.Callbacks.
3939 */
3940 public void bindAppWidget(final LauncherAppWidgetInfo item) {
3941 Runnable r = new Runnable() {
3942 public void run() {
3943 bindAppWidget(item);
3944 }
3945 };
3946 if (waitUntilResume(r)) {
3947 return;
3948 }
3949
3950 final long start = DEBUG_WIDGETS ? SystemClock.uptimeMillis() : 0;
3951 if (DEBUG_WIDGETS) {
3952 Log.d(TAG, "bindAppWidget: " + item);
3953 }
3954 final Workspace workspace = mWorkspace;
3955
3956 LauncherAppWidgetProviderInfo appWidgetInfo =
3957 LauncherModel.getProviderInfo(this, item.providerName, item.user);
3958
3959 if (!mIsSafeModeEnabled
3960 && ((item.restoreStatus & LauncherAppWidgetInfo.FLAG_PROVIDER_NOT_READY) == 0)
3961 && ((item.restoreStatus & LauncherAppWidgetInfo.FLAG_ID_NOT_VALID) != 0)) {
3962 if (appWidgetInfo == null) {
3963 if (DEBUG_WIDGETS) {
3964 Log.d(TAG, "Removing restored widget: id=" + item.appWidgetId
3965 + " belongs to component " + item.providerName
3966 + ", as the povider is null");
3967 }
3968 LauncherModel.deleteItemFromDatabase(this, item);
3969 return;
3970 }
3971 // Note: This assumes that the id remap broadcast is received before this step.
3972 // If that is not the case, the id remap will be ignored and user may see the
3973 // click to setup view.
3974 PendingAddWidgetInfo pendingInfo = new PendingAddWidgetInfo(appWidgetInfo, null);
3975 pendingInfo.spanX = item.spanX;
3976 pendingInfo.spanY = item.spanY;
3977 pendingInfo.minSpanX = item.minSpanX;
3978 pendingInfo.minSpanY = item.minSpanY;
3979 Bundle options = null;
3980 // AppsCustomizePagedView.getDefaultOptionsForWidget(this, pendingInfo);
3981
3982 int newWidgetId = mAppWidgetHost.allocateAppWidgetId();
3983 boolean success = mAppWidgetManager.bindAppWidgetIdIfAllowed(
3984 newWidgetId, appWidgetInfo, options);
3985
3986 // TODO consider showing a permission dialog when the widget is clicked.
3987 if (!success) {
3988 mAppWidgetHost.deleteAppWidgetId(newWidgetId);
3989 if (DEBUG_WIDGETS) {
3990 Log.d(TAG, "Removing restored widget: id=" + item.appWidgetId
3991 + " belongs to component " + item.providerName
3992 + ", as the launcher is unable to bing a new widget id");
3993 }
3994 LauncherModel.deleteItemFromDatabase(this, item);
3995 return;
3996 }
3997
3998 item.appWidgetId = newWidgetId;
3999
4000 // If the widget has a configure activity, it is still needs to set it up, otherwise
4001 // the widget is ready to go.
4002 item.restoreStatus = (appWidgetInfo.configure == null)
4003 ? LauncherAppWidgetInfo.RESTORE_COMPLETED
4004 : LauncherAppWidgetInfo.FLAG_UI_NOT_READY;
4005
4006 LauncherModel.updateItemInDatabase(this, item);
4007 }
4008
4009 if (!mIsSafeModeEnabled && item.restoreStatus == LauncherAppWidgetInfo.RESTORE_COMPLETED) {
4010 final int appWidgetId = item.appWidgetId;
4011 if (DEBUG_WIDGETS) {
4012 Log.d(TAG, "bindAppWidget: id=" + item.appWidgetId + " belongs to component "
4013 + appWidgetInfo.provider);
4014 }
4015
4016 item.hostView = mAppWidgetHost.createView(this, appWidgetId, appWidgetInfo);
4017 } else {
4018 appWidgetInfo = null;
4019 PendingAppWidgetHostView view = new PendingAppWidgetHostView(this, item,
4020 mIsSafeModeEnabled);
4021 view.updateIcon(mIconCache);
4022 item.hostView = view;
4023 item.hostView.updateAppWidget(null);
4024 item.hostView.setOnClickListener(this);
4025 }
4026
4027 item.hostView.setTag(item);
4028 item.onBindAppWidget(this);
4029
4030 workspace.addInScreen(item.hostView, item.container, item.screenId, item.cellX,
4031 item.cellY, item.spanX, item.spanY, false);
4032 if (!item.isCustomWidget()) {
4033 addWidgetToAutoAdvanceIfNeeded(item.hostView, appWidgetInfo);
4034 }
4035
4036 workspace.requestLayout();
4037
4038 if (DEBUG_WIDGETS) {
4039 Log.d(TAG, "bound widget id="+item.appWidgetId+" in "
4040 + (SystemClock.uptimeMillis()-start) + "ms");
4041 }
4042 }
4043
4044 /**
4045 * Restores a pending widget.
4046 *
4047 * @param appWidgetId The app widget id
4048 * @param cellInfo The position on screen where to create the widget.
4049 */
4050 private void completeRestoreAppWidget(final int appWidgetId) {
4051 LauncherAppWidgetHostView view = mWorkspace.getWidgetForAppWidgetId(appWidgetId);
4052 if ((view == null) || !(view instanceof PendingAppWidgetHostView)) {
4053 Log.e(TAG, "Widget update called, when the widget no longer exists.");
4054 return;
4055 }
4056
4057 LauncherAppWidgetInfo info = (LauncherAppWidgetInfo) view.getTag();
4058 info.restoreStatus = LauncherAppWidgetInfo.RESTORE_COMPLETED;
4059
4060 mWorkspace.reinflateWidgetsIfNecessary();
4061 LauncherModel.updateItemInDatabase(this, info);
4062 }
4063
4064 public void onPageBoundSynchronously(int page) {
4065 mSynchronouslyBoundPages.add(page);
4066 }
4067
4068 /**
4069 * Callback saying that there aren't any more items to bind.
4070 *
4071 * Implementation of the method from LauncherModel.Callbacks.
4072 */
4073 public void finishBindingItems() {
4074 Runnable r = new Runnable() {
4075 public void run() {
4076 finishBindingItems();
4077 }
4078 };
4079 if (waitUntilResume(r)) {
4080 return;
4081 }
4082 if (mSavedState != null) {
4083 if (!mWorkspace.hasFocus()) {
4084 mWorkspace.getChildAt(mWorkspace.getCurrentPage()).requestFocus();
4085 }
4086 mSavedState = null;
4087 }
4088
4089 mWorkspace.restoreInstanceStateForRemainingPages();
4090
4091 setWorkspaceLoading(false);
4092 sendLoadingCompleteBroadcastIfNecessary();
4093
4094 // If we received the result of any pending adds while the loader was running (e.g. the
4095 // widget configuration forced an orientation change), process them now.
4096 if (sPendingAddItem != null) {
4097 final long screenId = completeAdd(sPendingAddItem);
4098
4099 // TODO: this moves the user to the page where the pending item was added. Ideally,
4100 // the screen would be guaranteed to exist after bind, and the page would be set through
4101 // the workspace restore process.
4102 mWorkspace.post(new Runnable() {
4103 @Override
4104 public void run() {
4105 mWorkspace.snapToScreenId(screenId);
4106 }
4107 });
4108 sPendingAddItem = null;
4109 }
4110
4111 PackageInstallerCompat.getInstance(this).onFinishBind();
4112
4113 if (mLauncherCallbacks != null) {
4114 mLauncherCallbacks.finishBindingItems(false);
4115 }
4116 }
4117
4118 private void sendLoadingCompleteBroadcastIfNecessary() {
4119 if (!mSharedPrefs.getBoolean(FIRST_LOAD_COMPLETE, false)) {
4120 String permission =
4121 getResources().getString(R.string.receive_first_load_broadcast_permission);
4122 Intent intent = new Intent(ACTION_FIRST_LOAD_COMPLETE);
4123 sendBroadcast(intent, permission);
4124 SharedPreferences.Editor editor = mSharedPrefs.edit();
4125 editor.putBoolean(FIRST_LOAD_COMPLETE, true);
4126 editor.apply();
4127 }
4128 }
4129
4130 public boolean isAllAppsButtonRank(int rank) {
4131 if (mHotseat != null) {
4132 return mHotseat.isAllAppsButtonRank(rank);
4133 }
4134 return false;
4135 }
4136
4137 private boolean canRunNewAppsAnimation() {
4138 long diff = System.currentTimeMillis() - mDragController.getLastGestureUpTime();
4139 return diff > (NEW_APPS_ANIMATION_INACTIVE_TIMEOUT_SECONDS * 1000);
4140 }
4141
4142 private ValueAnimator createNewAppBounceAnimation(View v, int i) {
4143 ValueAnimator bounceAnim = LauncherAnimUtils.ofPropertyValuesHolder(v,
4144 PropertyValuesHolder.ofFloat("alpha", 1f),
4145 PropertyValuesHolder.ofFloat("scaleX", 1f),
4146 PropertyValuesHolder.ofFloat("scaleY", 1f));
4147 bounceAnim.setDuration(InstallShortcutReceiver.NEW_SHORTCUT_BOUNCE_DURATION);
4148 bounceAnim.setStartDelay(i * InstallShortcutReceiver.NEW_SHORTCUT_STAGGER_DELAY);
4149 bounceAnim.setInterpolator(new SmoothPagedView.OvershootInterpolator());
4150 return bounceAnim;
4151 }
4152
4153 public boolean useVerticalBarLayout() {
4154 return LauncherAppState.getInstance().getDynamicGrid().
4155 getDeviceProfile().isVerticalBarLayout();
4156 }
4157
4158 protected Rect getSearchBarBounds() {
4159 return LauncherAppState.getInstance().getDynamicGrid().
4160 getDeviceProfile().getSearchBarBounds();
4161 }
4162
4163 public void bindSearchablesChanged() {
4164 if (mSearchDropTargetBar == null) {
4165 return;
4166 }
4167 if (mQsb != null) {
4168 mSearchDropTargetBar.removeView(mQsb);
4169 mQsb = null;
4170 }
4171 mSearchDropTargetBar.setQsbSearchBar(getOrCreateQsbBar());
4172 }
4173
4174 /**
4175 * Add the icons for all apps.
4176 *
4177 * Implementation of the method from LauncherModel.Callbacks.
4178 */
4179 public void bindAllApplications(final ArrayList<AppInfo> apps) {
4180 if (mAppsView != null) {
4181 mAppsView.setApps(apps);
4182 }
4183 if (mWidgetsView != null) {
4184 mWidgetsView.addWidgets(LauncherModel.getSortedWidgetsAndShortcuts(this, false),
4185 getPackageManager());
4186 }
4187 if (mLauncherCallbacks != null) {
4188 mLauncherCallbacks.bindAllApplications(apps);
4189 }
4190 }
4191
4192 /**
4193 * A package was updated.
4194 *
4195 * Implementation of the method from LauncherModel.Callbacks.
4196 */
4197 public void bindAppsUpdated(final ArrayList<AppInfo> apps) {
4198 Runnable r = new Runnable() {
4199 public void run() {
4200 bindAppsUpdated(apps);
4201 }
4202 };
4203 if (waitUntilResume(r)) {
4204 return;
4205 }
4206
4207 if (mAppsView != null) {
4208 mAppsView.updateApps(apps);
4209 }
4210 }
4211
4212 @Override
4213 public void bindWidgetsRestored(final ArrayList<LauncherAppWidgetInfo> widgets) {
4214 Runnable r = new Runnable() {
4215 public void run() {
4216 bindWidgetsRestored(widgets);
4217 }
4218 };
4219 if (waitUntilResume(r)) {
4220 return;
4221 }
4222 mWorkspace.widgetsRestored(widgets);
4223 }
4224
4225 /**
4226 * Some shortcuts were updated in the background.
4227 *
4228 * Implementation of the method from LauncherModel.Callbacks.
4229 */
4230 @Override
4231 public void bindShortcutsChanged(final ArrayList<ShortcutInfo> updated,
4232 final ArrayList<ShortcutInfo> removed, final UserHandleCompat user) {
4233 Runnable r = new Runnable() {
4234 public void run() {
4235 bindShortcutsChanged(updated, removed, user);
4236 }
4237 };
4238 if (waitUntilResume(r)) {
4239 return;
4240 }
4241
4242 if (!updated.isEmpty()) {
4243 mWorkspace.updateShortcuts(updated);
4244 }
4245
4246 if (!removed.isEmpty()) {
4247 HashSet<ComponentName> removedComponents = new HashSet<ComponentName>();
4248 for (ShortcutInfo si : removed) {
4249 removedComponents.add(si.getTargetComponent());
4250 }
4251 mWorkspace.removeItemsByComponentName(removedComponents, user);
4252 // Notify the drag controller
4253 mDragController.onAppsRemoved(new ArrayList<String>(), removedComponents);
4254 }
4255 }
4256
4257 /**
4258 * Update the state of a package, typically related to install state.
4259 *
4260 * Implementation of the method from LauncherModel.Callbacks.
4261 */
4262 @Override
4263 public void updatePackageState(ArrayList<PackageInstallInfo> installInfo) {
4264 if (mWorkspace != null) {
4265 mWorkspace.updatePackageState(installInfo);
4266 }
4267 }
4268
4269 /**
4270 * Update the label and icon of all the icons in a package
4271 *
4272 * Implementation of the method from LauncherModel.Callbacks.
4273 */
4274 @Override
4275 public void updatePackageBadge(String packageName) {
4276 if (mWorkspace != null) {
4277 mWorkspace.updatePackageBadge(packageName, UserHandleCompat.myUserHandle());
4278 }
4279 }
4280
4281 /**
4282 * A package was uninstalled. We take both the super set of packageNames
4283 * in addition to specific applications to remove, the reason being that
4284 * this can be called when a package is updated as well. In that scenario,
4285 * we only remove specific components from the workspace, where as
4286 * package-removal should clear all items by package name.
4287 *
4288 * @param reason if non-zero, the icons are not permanently removed, rather marked as disabled.
4289 * Implementation of the method from LauncherModel.Callbacks.
4290 */
4291 @Override
4292 public void bindComponentsRemoved(final ArrayList<String> packageNames,
4293 final ArrayList<AppInfo> appInfos, final UserHandleCompat user, final int reason) {
4294 Runnable r = new Runnable() {
4295 public void run() {
4296 bindComponentsRemoved(packageNames, appInfos, user, reason);
4297 }
4298 };
4299 if (waitUntilResume(r)) {
4300 return;
4301 }
4302
4303 if (reason == 0) {
4304 HashSet<ComponentName> removedComponents = new HashSet<ComponentName>();
4305 for (AppInfo info : appInfos) {
4306 removedComponents.add(info.componentName);
4307 }
4308 if (!packageNames.isEmpty()) {
4309 mWorkspace.removeItemsByPackageName(packageNames, user);
4310 }
4311 if (!removedComponents.isEmpty()) {
4312 mWorkspace.removeItemsByComponentName(removedComponents, user);
4313 }
4314 // Notify the drag controller
4315 mDragController.onAppsRemoved(packageNames, removedComponents);
4316
4317 } else {
4318 mWorkspace.disableShortcutsByPackageName(packageNames, user, reason);
4319 }
4320
4321 // Update AllApps
4322 if (mAppsView != null) {
4323 mAppsView.removeApps(appInfos);
4324 }
4325 }
4326
4327 /**
4328 * A number of packages were updated.
4329 */
4330 @Thunk ArrayList<Object> mWidgetsAndShortcuts;
4331 private Runnable mBindPackagesUpdatedRunnable = new Runnable() {
4332 public void run() {
4333 bindPackagesUpdated(mWidgetsAndShortcuts);
4334 mWidgetsAndShortcuts = null;
4335 }
4336 };
4337
4338 public void bindPackagesUpdated(final ArrayList<Object> widgetsAndShortcuts) {
4339 if (waitUntilResume(mBindPackagesUpdatedRunnable, true)) {
4340 mWidgetsAndShortcuts = widgetsAndShortcuts;
4341 return;
4342 }
4343
4344 if (mWidgetsView != null) {
4345 mWidgetsView.addWidgets(LauncherModel.getSortedWidgetsAndShortcuts(this, false),
4346 getPackageManager());
4347 }
4348 }
4349
4350 private int mapConfigurationOriActivityInfoOri(int configOri) {
4351 final Display d = getWindowManager().getDefaultDisplay();
4352 int naturalOri = Configuration.ORIENTATION_LANDSCAPE;
4353 switch (d.getRotation()) {
4354 case Surface.ROTATION_0:
4355 case Surface.ROTATION_180:
4356 // We are currently in the same basic orientation as the natural orientation
4357 naturalOri = configOri;
4358 break;
4359 case Surface.ROTATION_90:
4360 case Surface.ROTATION_270:
4361 // We are currently in the other basic orientation to the natural orientation
4362 naturalOri = (configOri == Configuration.ORIENTATION_LANDSCAPE) ?
4363 Configuration.ORIENTATION_PORTRAIT : Configuration.ORIENTATION_LANDSCAPE;
4364 break;
4365 }
4366
4367 int[] oriMap = {
4368 ActivityInfo.SCREEN_ORIENTATION_PORTRAIT,
4369 ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE,
4370 ActivityInfo.SCREEN_ORIENTATION_REVERSE_PORTRAIT,
4371 ActivityInfo.SCREEN_ORIENTATION_REVERSE_LANDSCAPE
4372 };
4373 // Since the map starts at portrait, we need to offset if this device's natural orientation
4374 // is landscape.
4375 int indexOffset = 0;
4376 if (naturalOri == Configuration.ORIENTATION_LANDSCAPE) {
4377 indexOffset = 1;
4378 }
4379 return oriMap[(d.getRotation() + indexOffset) % 4];
4380 }
4381
4382 public void lockScreenOrientation() {
4383 if (Utilities.isRotationEnabled(this)) {
4384 if (Build.VERSION.SDK_INT < Build.VERSION_CODES.JELLY_BEAN_MR2) {
4385 setRequestedOrientation(mapConfigurationOriActivityInfoOri(getResources()
4386 .getConfiguration().orientation));
4387 } else {
4388 setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_LOCKED);
4389 }
4390 }
4391 }
4392 public void unlockScreenOrientation(boolean immediate) {
4393 if (Utilities.isRotationEnabled(this)) {
4394 if (immediate) {
4395 setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED);
4396 } else {
4397 mHandler.postDelayed(new Runnable() {
4398 public void run() {
4399 setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED);
4400 }
4401 }, mRestoreScreenOrientationDelay);
4402 }
4403 }
4404 }
4405
4406 protected boolean isLauncherPreinstalled() {
4407 if (mLauncherCallbacks != null) {
4408 return mLauncherCallbacks.isLauncherPreinstalled();
4409 }
4410 PackageManager pm = getPackageManager();
4411 try {
4412 ApplicationInfo ai = pm.getApplicationInfo(getComponentName().getPackageName(), 0);
4413 if ((ai.flags & ApplicationInfo.FLAG_SYSTEM) != 0) {
4414 return true;
4415 } else {
4416 return false;
4417 }
4418 } catch (NameNotFoundException e) {
4419 e.printStackTrace();
4420 return false;
4421 }
4422 }
4423
4424 /**
4425 * This method indicates whether or not we should suggest default wallpaper dimensions
4426 * when our wallpaper cropper was not yet used to set a wallpaper.
4427 */
4428 protected boolean overrideWallpaperDimensions() {
4429 if (mLauncherCallbacks != null) {
4430 return mLauncherCallbacks.overrideWallpaperDimensions();
4431 }
4432 return true;
4433 }
4434
4435 /**
4436 * To be overridden by subclasses to indicate that there is an activity to launch
4437 * before showing the standard launcher experience.
4438 */
4439 protected boolean hasFirstRunActivity() {
4440 if (mLauncherCallbacks != null) {
4441 return mLauncherCallbacks.hasFirstRunActivity();
4442 }
4443 return false;
4444 }
4445
4446 /**
4447 * To be overridden by subclasses to launch any first run activity
4448 */
4449 protected Intent getFirstRunActivity() {
4450 if (mLauncherCallbacks != null) {
4451 return mLauncherCallbacks.getFirstRunActivity();
4452 }
4453 return null;
4454 }
4455
4456 /**
4457 * Returns whether the launcher callbacks overrides search in all apps.
4458 * @return
4459 */
4460 @Thunk boolean isAllAppsSearchOverridden() {
4461 if (mLauncherCallbacks != null) {
4462 return mLauncherCallbacks.overrideAllAppsSearch();
4463 }
4464 return false;
4465 }
4466
4467 private boolean shouldRunFirstRunActivity() {
4468 return !ActivityManager.isRunningInTestHarness() &&
4469 !mSharedPrefs.getBoolean(FIRST_RUN_ACTIVITY_DISPLAYED, false);
4470 }
4471
4472 protected boolean hasRunFirstRunActivity() {
4473 return mSharedPrefs.getBoolean(FIRST_RUN_ACTIVITY_DISPLAYED, false);
4474 }
4475
4476 public boolean showFirstRunActivity() {
4477 if (shouldRunFirstRunActivity() &&
4478 hasFirstRunActivity()) {
4479 Intent firstRunIntent = getFirstRunActivity();
4480 if (firstRunIntent != null) {
4481 startActivity(firstRunIntent);
4482 markFirstRunActivityShown();
4483 return true;
4484 }
4485 }
4486 return false;
4487 }
4488
4489 private void markFirstRunActivityShown() {
4490 SharedPreferences.Editor editor = mSharedPrefs.edit();
4491 editor.putBoolean(FIRST_RUN_ACTIVITY_DISPLAYED, true);
4492 editor.apply();
4493 }
4494
4495 /**
4496 * To be overridden by subclasses to indicate that there is an in-activity full-screen intro
4497 * screen that must be displayed and dismissed.
4498 */
4499 protected boolean hasDismissableIntroScreen() {
4500 if (mLauncherCallbacks != null) {
4501 return mLauncherCallbacks.hasDismissableIntroScreen();
4502 }
4503 return false;
4504 }
4505
4506 /**
4507 * Full screen intro screen to be shown and dismissed before the launcher can be used.
4508 */
4509 protected View getIntroScreen() {
4510 if (mLauncherCallbacks != null) {
4511 return mLauncherCallbacks.getIntroScreen();
4512 }
4513 return null;
4514 }
4515
4516 /**
4517 * To be overriden by subclasses to indicate whether the in-activity intro screen has been
4518 * dismissed. This method is ignored if #hasDismissableIntroScreen returns false.
4519 */
4520 private boolean shouldShowIntroScreen() {
4521 return hasDismissableIntroScreen() &&
4522 !mSharedPrefs.getBoolean(INTRO_SCREEN_DISMISSED, false);
4523 }
4524
4525 protected void showIntroScreen() {
4526 View introScreen = getIntroScreen();
4527 changeWallpaperVisiblity(false);
4528 if (introScreen != null) {
4529 mDragLayer.showOverlayView(introScreen);
4530 }
4531 if (mLauncherOverlayContainer != null) {
4532 mLauncherOverlayContainer.setVisibility(View.INVISIBLE);
4533 }
4534 }
4535
4536 public void dismissIntroScreen() {
4537 markIntroScreenDismissed();
4538 if (showFirstRunActivity()) {
4539 // We delay hiding the intro view until the first run activity is showing. This
4540 // avoids a blip.
4541 mWorkspace.postDelayed(new Runnable() {
4542 @Override
4543 public void run() {
4544 mDragLayer.dismissOverlayView();
4545 if (mLauncherOverlayContainer != null) {
4546 mLauncherOverlayContainer.setVisibility(View.VISIBLE);
4547 }
4548 showFirstRunClings();
4549 }
4550 }, ACTIVITY_START_DELAY);
4551 } else {
4552 mDragLayer.dismissOverlayView();
4553 if (mLauncherOverlayContainer != null) {
4554 mLauncherOverlayContainer.setVisibility(View.VISIBLE);
4555 }
4556 showFirstRunClings();
4557 }
4558 changeWallpaperVisiblity(true);
4559 }
4560
4561 private void markIntroScreenDismissed() {
4562 SharedPreferences.Editor editor = mSharedPrefs.edit();
4563 editor.putBoolean(INTRO_SCREEN_DISMISSED, true);
4564 editor.apply();
4565 }
4566
4567 @Thunk void showFirstRunClings() {
4568 // The two first run cling paths are mutually exclusive, if the launcher is preinstalled
4569 // on the device, then we always show the first run cling experience (or if there is no
4570 // launcher2). Otherwise, we prompt the user upon started for migration
4571 LauncherClings launcherClings = new LauncherClings(this);
4572 if (launcherClings.shouldShowFirstRunOrMigrationClings()) {
4573 if (mModel.canMigrateFromOldLauncherDb(this)) {
4574 launcherClings.showMigrationCling();
4575 } else {
4576 launcherClings.showLongPressCling(true);
4577 }
4578 }
4579 }
4580
4581 void showWorkspaceSearchAndHotseat() {
4582 if (mWorkspace != null) mWorkspace.setAlpha(1f);
4583 if (mHotseat != null) mHotseat.setAlpha(1f);
4584 if (mPageIndicators != null) mPageIndicators.setAlpha(1f);
4585 if (mSearchDropTargetBar != null) mSearchDropTargetBar.showSearchBar(false);
4586 }
4587
4588 void hideWorkspaceSearchAndHotseat() {
4589 if (mWorkspace != null) mWorkspace.setAlpha(0f);
4590 if (mHotseat != null) mHotseat.setAlpha(0f);
4591 if (mPageIndicators != null) mPageIndicators.setAlpha(0f);
4592 if (mSearchDropTargetBar != null) mSearchDropTargetBar.hideSearchBar(false);
4593 }
4594
4595 public ItemInfo createAppDragInfo(Intent appLaunchIntent) {
4596 // Called from search suggestion, not supported in other profiles.
4597 final UserHandleCompat myUser = UserHandleCompat.myUserHandle();
4598 LauncherAppsCompat launcherApps = LauncherAppsCompat.getInstance(this);
4599 LauncherActivityInfoCompat activityInfo = launcherApps.resolveActivity(appLaunchIntent,
4600 myUser);
4601 if (activityInfo == null) {
4602 return null;
4603 }
4604 return new AppInfo(this, activityInfo, myUser, mIconCache);
4605 }
4606
4607 public ItemInfo createShortcutDragInfo(Intent shortcutIntent, CharSequence caption,
4608 Bitmap icon) {
4609 // Called from search suggestion, not supported in other profiles.
4610 return createShortcutDragInfo(shortcutIntent, caption, icon,
4611 UserHandleCompat.myUserHandle());
4612 }
4613
4614 public ItemInfo createShortcutDragInfo(Intent shortcutIntent, CharSequence caption,
4615 Bitmap icon, UserHandleCompat user) {
4616 UserManagerCompat userManager = UserManagerCompat.getInstance(this);
4617 CharSequence contentDescription = userManager.getBadgedLabelForUser(caption, user);
4618 return new ShortcutInfo(shortcutIntent, caption, contentDescription, icon, user);
4619 }
4620
4621 protected void moveWorkspaceToDefaultScreen() {
4622 mWorkspace.moveToDefaultScreen(false);
4623 }
4624
4625 public void startDrag(View dragView, ItemInfo dragInfo, DragSource source) {
4626 dragView.setTag(dragInfo);
4627 mWorkspace.onExternalDragStartedWithItem(dragView);
4628 mWorkspace.beginExternalDragShared(dragView, source);
4629 }
4630
4631 @Override
4632 public void onPageSwitch(View newPage, int newPageIndex) {
4633 if (mLauncherCallbacks != null) {
4634 mLauncherCallbacks.onPageSwitch(newPage, newPageIndex);
4635 }
4636 }
4637
4638 /**
4639 * Prints out out state for debugging.
4640 */
4641 public void dumpState() {
4642 Log.d(TAG, "BEGIN launcher3 dump state for launcher " + this);
4643 Log.d(TAG, "mSavedState=" + mSavedState);
4644 Log.d(TAG, "mWorkspaceLoading=" + mWorkspaceLoading);
4645 Log.d(TAG, "mRestoring=" + mRestoring);
4646 Log.d(TAG, "mWaitingForResult=" + mWaitingForResult);
4647 Log.d(TAG, "mSavedInstanceState=" + mSavedInstanceState);
4648 Log.d(TAG, "sFolders.size=" + sFolders.size());
4649 mModel.dumpState();
4650 // TODO(hyunyoungs): add mWidgetsView.dumpState(); or mWidgetsModel.dumpState();
4651
4652 Log.d(TAG, "END launcher3 dump state");
4653 }
4654
4655 @Override
4656 public void dump(String prefix, FileDescriptor fd, PrintWriter writer, String[] args) {
4657 super.dump(prefix, fd, writer, args);
4658 synchronized (sDumpLogs) {
4659 writer.println(" ");
4660 writer.println("Debug logs: ");
4661 for (int i = 0; i < sDumpLogs.size(); i++) {
4662 writer.println(" " + sDumpLogs.get(i));
4663 }
4664 }
4665 if (mLauncherCallbacks != null) {
4666 mLauncherCallbacks.dump(prefix, fd, writer, args);
4667 }
4668 }
4669
4670 public static void dumpDebugLogsToConsole() {
4671 if (DEBUG_DUMP_LOG) {
4672 synchronized (sDumpLogs) {
4673 Log.d(TAG, "");
4674 Log.d(TAG, "*********************");
4675 Log.d(TAG, "Launcher debug logs: ");
4676 for (int i = 0; i < sDumpLogs.size(); i++) {
4677 Log.d(TAG, " " + sDumpLogs.get(i));
4678 }
4679 Log.d(TAG, "*********************");
4680 Log.d(TAG, "");
4681 }
4682 }
4683 }
4684
4685 public static void addDumpLog(String tag, String log, boolean debugLog) {
4686 addDumpLog(tag, log, null, debugLog);
4687 }
4688
4689 public static void addDumpLog(String tag, String log, Exception e, boolean debugLog) {
4690 if (debugLog) {
4691 if (e != null) {
4692 Log.d(tag, log, e);
4693 } else {
4694 Log.d(tag, log);
4695 }
4696 }
4697 if (DEBUG_DUMP_LOG) {
4698 sDateStamp.setTime(System.currentTimeMillis());
4699 synchronized (sDumpLogs) {
4700 sDumpLogs.add(sDateFormat.format(sDateStamp) + ": " + tag + ", " + log
4701 + (e == null ? "" : (", Exception: " + e)));
4702 }
4703 }
4704 }
4705
4706 public static CustomAppWidget getCustomAppWidget(String name) {
4707 return sCustomAppWidgets.get(name);
4708 }
4709
4710 public static HashMap<String, CustomAppWidget> getCustomAppWidgets() {
4711 return sCustomAppWidgets;
4712 }
4713
4714 public void dumpLogsToLocalData() {
4715 if (DEBUG_DUMP_LOG) {
4716 new AsyncTask<Void, Void, Void>() {
4717 public Void doInBackground(Void ... args) {
4718 boolean success = false;
4719 sDateStamp.setTime(sRunStart);
4720 String FILENAME = sDateStamp.getMonth() + "-"
4721 + sDateStamp.getDay() + "_"
4722 + sDateStamp.getHours() + "-"
4723 + sDateStamp.getMinutes() + "_"
4724 + sDateStamp.getSeconds() + ".txt";
4725
4726 FileOutputStream fos = null;
4727 File outFile = null;
4728 try {
4729 outFile = new File(getFilesDir(), FILENAME);
4730 outFile.createNewFile();
4731 fos = new FileOutputStream(outFile);
4732 } catch (Exception e) {
4733 e.printStackTrace();
4734 }
4735 if (fos != null) {
4736 PrintWriter writer = new PrintWriter(fos);
4737
4738 writer.println(" ");
4739 writer.println("Debug logs: ");
4740 synchronized (sDumpLogs) {
4741 for (int i = 0; i < sDumpLogs.size(); i++) {
4742 writer.println(" " + sDumpLogs.get(i));
4743 }
4744 }
4745 writer.close();
4746 }
4747 try {
4748 if (fos != null) {
4749 fos.close();
4750 success = true;
4751 }
4752 } catch (IOException e) {
4753 e.printStackTrace();
4754 }
4755 return null;
4756 }
4757 }.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR, (Void) null);
4758 }
4759 }
4760 }
4761
4762 interface LauncherTransitionable {
4763 View getContent();
4764 void onLauncherTransitionPrepare(Launcher l, boolean animated, boolean toWorkspace);
4765 void onLauncherTransitionStart(Launcher l, boolean animated, boolean toWorkspace);
4766 void onLauncherTransitionStep(Launcher l, float t);
4767 void onLauncherTransitionEnd(Launcher l, boolean animated, boolean toWorkspace);
4768 }
4769
4770 interface DebugIntents {
4771 static final String DELETE_DATABASE = "com.android.launcher3.action.DELETE_DATABASE";
4772 static final String MIGRATE_DATABASE = "com.android.launcher3.action.MIGRATE_DATABASE";
4773 }
|
1 /*
2 * Copyright (C) 2008 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 * http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16 package com.android.launcher3;
17
18 import android.animation.Animator;
19 import android.animation.AnimatorListenerAdapter;
20 import android.animation.AnimatorSet;
21 import android.animation.ObjectAnimator;
22 import android.animation.PropertyValuesHolder;
23 import android.animation.ValueAnimator;
24 import android.annotation.TargetApi;
25 import android.app.Activity;
26 import android.app.ActivityManager;
27 import android.app.ActivityOptions;
28 import android.app.AlertDialog;
29 import android.app.SearchManager;
30 import android.appwidget.AppWidgetHostView;
31 import android.appwidget.AppWidgetManager;
32 import android.appwidget.AppWidgetProviderInfo;
33 import android.content.ActivityNotFoundException;
34 import android.content.BroadcastReceiver;
35 import android.content.ComponentCallbacks2;
36 import android.content.ComponentName;
37 import android.content.ContentResolver;
38 import android.content.Context;
39 import android.content.DialogInterface;
40 import android.content.Intent;
41 import android.content.IntentFilter;
42 import android.content.IntentSender;
43 import android.content.SharedPreferences;
44 import android.content.pm.ActivityInfo;
45 import android.content.pm.ApplicationInfo;
46 import android.content.pm.PackageManager.NameNotFoundException;
47 import android.content.pm.PackageManager;
48 import android.content.res.Configuration;
49 import android.database.ContentObserver;
50 import android.database.sqlite.SQLiteDatabase;
51 import android.graphics.Bitmap;
52 import android.graphics.Canvas;
53 import android.graphics.Color;
54 import android.graphics.PorterDuff;
55 import android.graphics.Rect;
56 import android.graphics.drawable.Drawable;
57 import android.net.Uri;
58 import android.os.AsyncTask;
59 import android.os.Build;
60 import android.os.Bundle;
61 import android.os.Environment;
62 import android.os.Handler;
63 import android.os.Message;
64 import android.os.StrictMode;
65 import android.os.SystemClock;
66 import android.text.Selection;
67 import android.text.SpannableStringBuilder;
68 import android.text.TextUtils;
69 import android.text.method.TextKeyListener;
70 import android.util.Log;
71 import android.view.Display;
72 import android.view.Gravity;
73 import android.view.HapticFeedbackConstants;
74 import android.view.KeyEvent;
75 import android.view.LayoutInflater;
76 import android.view.Menu;
77 import android.view.MotionEvent;
78 import android.view.Surface;
79 import android.view.View.OnClickListener;
80 import android.view.View.OnLongClickListener;
81 import android.view.View;
82 import android.view.ViewGroup;
83 import android.view.ViewStub;
84 import android.view.ViewTreeObserver;
85 import android.view.Window;
86 import android.view.WindowManager;
87 import android.view.accessibility.AccessibilityEvent;
88 import android.view.inputmethod.InputMethodManager;
89 import android.widget.Advanceable;
90 import android.widget.FrameLayout;
91 import android.widget.ImageView;
92 import android.widget.TextView;
93 import android.widget.Toast;
94 import com.android.launcher3.DropTarget.DragObject;
95 import com.android.launcher3.PagedView.PageSwitchListener;
96 import com.android.launcher3.compat.AppWidgetManagerCompat;
97 import com.android.launcher3.compat.LauncherActivityInfoCompat;
98 import com.android.launcher3.compat.LauncherAppsCompat;
99 import com.android.launcher3.compat.PackageInstallerCompat.PackageInstallInfo;
100 import com.android.launcher3.compat.PackageInstallerCompat;
101 import com.android.launcher3.compat.UserHandleCompat;
102 import com.android.launcher3.compat.UserManagerCompat;
103 import com.android.launcher3.util.Thunk;
104 import com.android.launcher3.widget.PendingAddWidgetInfo;
105 import com.android.launcher3.widget.WidgetsContainerView;
106 import java.io.DataInputStream;
107 import java.io.DataOutputStream;
108 import java.io.File;
109 import java.io.FileDescriptor;
110 import java.io.FileNotFoundException;
111 import java.io.FileOutputStream;
112 import java.io.IOException;
113 import java.io.PrintWriter;
114 import java.lang.reflect.InvocationTargetException;
115 import java.lang.reflect.Method;
116 import java.text.DateFormat;
117 import java.util.ArrayList;
118 import java.util.Collection;
119 import java.util.Date;
120 import java.util.HashMap;
121 import java.util.HashSet;
122 import java.util.List;
123 import java.util.concurrent.atomic.AtomicInteger;
124
125
126 interface DebugIntents {
127 public static final String DELETE_DATABASE = "com.android.launcher3.action.DELETE_DATABASE";
128
129 public static final String MIGRATE_DATABASE = "com.android.launcher3.action.MIGRATE_DATABASE";
130 }
131
132 /**
133 * Default launcher application.
134 */
135 public class Launcher extends Activity implements View.OnClickListener , OnLongClickListener , LauncherMo🔵
136 static final String TAG = "Launcher";
137
138 static final boolean LOGD = true;
139
140 static final boolean PROFILE_STARTUP = false;
141
142 static final boolean DEBUG_WIDGETS = true;
143
144 static final boolean DEBUG_STRICT_MODE = false;
145
146 static final boolean DEBUG_RESUME_TIME = false;
147
148 static final boolean DEBUG_DUMP_LOG = false;
149
150 // allow DebugIntents to run
151 static final boolean ENABLE_DEBUG_INTENTS = false; // allow DebugIntents to run
152
153 private static final int REQUEST_CREATE_SHORTCUT = 1;
154
155 private static final int REQUEST_CREATE_APPWIDGET = 5;
156
157 private static final int REQUEST_PICK_APPWIDGET = 9;
158
159 private static final int REQUEST_PICK_WALLPAPER = 10;
160
161 private static final int REQUEST_BIND_APPWIDGET = 11;
162
163 private static final int REQUEST_RECONFIGURE_APPWIDGET = 12;
164
165 /**
166 * IntentStarter uses request codes starting with this. This must be greater than all activity
167 * request codes used internally.
168 */
169 protected static final int REQUEST_LAST = 100;
170
171 static final String EXTRA_SHORTCUT_DUPLICATE = "duplicate";
172
173 static final int SCREEN_COUNT = 5;
174
175 // To turn on these properties, type
176 // adb shell setprop log.tag.PROPERTY_NAME [VERBOSE | SUPPRESS]
177 static final String DUMP_STATE_PROPERTY = "launcher_dump_state";
178
179 // The Intent extra that defines whether to ignore the launch animation
180 // The Intent extra that defines whether to ignore the launch animation
181 static final String INTENT_EXTRA_IGNORE_LAUNCH_ANIMATION =
182 "com.android.launcher3.intent.extra.shortcut.INGORE_LAUNCH_ANIMATION";
183
184 // Type: int
185 // Type: int
186 private static final String RUNTIME_STATE_CURRENT_SCREEN = "launcher.current_screen";
187
188 // Type: int
189 // Type: int
190 private static final String RUNTIME_STATE = "launcher.state";
191
192 // Type: int
193 // Type: int
194 private static final String RUNTIME_STATE_PENDING_ADD_CONTAINER = "launcher.add_container";
195
196 // Type: int
197 // Type: int
198 private static final String RUNTIME_STATE_PENDING_ADD_SCREEN = "launcher.add_screen";
199
200 // Type: int
201 // Type: int
202 private static final String RUNTIME_STATE_PENDING_ADD_CELL_X = "launcher.add_cell_x";
203
204 // Type: int
205 // Type: int
206 private static final String RUNTIME_STATE_PENDING_ADD_CELL_Y = "launcher.add_cell_y";
207
208 // Type: boolean
209 // Type: boolean
210 private static final String RUNTIME_STATE_PENDING_FOLDER_RENAME = "launcher.rename_folder";
211
212 // Type: long
213 // Type: long
214 private static final String RUNTIME_STATE_PENDING_FOLDER_RENAME_ID = "launcher.rename_folder_id";
215
216 // Type: int
217 // Type: int
218 private static final String RUNTIME_STATE_PENDING_ADD_SPAN_X = "launcher.add_span_x";
219
220 // Type: int
221 // Type: int
222 private static final String RUNTIME_STATE_PENDING_ADD_SPAN_Y = "launcher.add_span_y";
223
224 // Type: parcelable
225 // Type: parcelable
226 private static final String RUNTIME_STATE_PENDING_ADD_WIDGET_INFO = "launcher.add_widget_info";
227
228 // Type: parcelable
229 // Type: parcelable
230 private static final String RUNTIME_STATE_PENDING_ADD_WIDGET_ID = "launcher.add_widget_id";
231
232 // Type: int[]
233 // Type: int[]
234 private static final String RUNTIME_STATE_VIEW_IDS = "launcher.view_ids";
235
236 static final String INTRO_SCREEN_DISMISSED = "launcher.intro_screen_dismissed";
237
238 static final String FIRST_RUN_ACTIVITY_DISPLAYED = "launcher.first_run_activity_displayed";
239
240 static final String FIRST_LOAD_COMPLETE = "launcher.first_load_complete";
241
242 static final String ACTION_FIRST_LOAD_COMPLETE =
243 "com.android.launcher3.action.FIRST_LOAD_COMPLETE";
244
245 public static final String SHOW_WEIGHT_WATCHER = "debug.show_mem";
246
247 public static final boolean SHOW_WEIGHT_WATCHER_DEFAULT = false;
248
249 private static final String QSB_WIDGET_ID = "qsb_widget_id";
250
251 private static final String QSB_WIDGET_PROVIDER = "qsb_widget_provider";
252
253 public static final String USER_HAS_MIGRATED = "launcher.user_migrated_from_old_data";
254
255 /** The different states that Launcher can be in. */
256 enum State {
257
258 NONE,
259 WORKSPACE,
260 APPS,
261 APPS_SPRING_LOADED,
262 WIDGETS,
263 WIDGETS_SPRING_LOADED;}
264
265 @Thunk
266 State mState = State.WORKSPACE;
267
268 @Thunk
269 AnimatorSet mStateAnimation;
270
271 @Thunk
272 LauncherStateTransitionAnimation mStateTransitionAnimation;
273
274 private boolean mIsSafeModeEnabled;
275
276 LauncherOverlayCallbacks mLauncherOverlayCallbacks = new LauncherOverlayCallbacksImpl();
277
278 LauncherOverlay mLauncherOverlay;
279
280 InsettableFrameLayout mLauncherOverlayContainer;
281
282 static final int APPWIDGET_HOST_ID = 1024;
283
284 public static final int EXIT_SPRINGLOADED_MODE_SHORT_TIMEOUT = 300;
285
286 private static final int ON_ACTIVITY_RESULT_ANIMATION_DELAY = 500;
287
288 private static final int ACTIVITY_START_DELAY = 1000;
289
290 private HashMap<Integer, Integer> mItemIdToViewId = new HashMap<Integer, Integer>();
291
292 private static final AtomicInteger sNextGeneratedId = new AtomicInteger(1);
293
294 // How long to wait before the new-shortcut animation automatically pans the workspace
295 // How long to wait before the new-shortcut animation automatically pans the workspace
296 private static int NEW_APPS_PAGE_MOVE_DELAY = 500;
297
298 private static int NEW_APPS_ANIMATION_INACTIVE_TIMEOUT_SECONDS = 5;
299
300 @Thunk
301 static int NEW_APPS_ANIMATION_DELAY = 500;
302
303 private final BroadcastReceiver mCloseSystemDialogsReceiver
304 = new CloseSystemDialogsIntentReceiver();
305
306 private final ContentObserver mWidgetObserver = new AppWidgetResetObserver();
307
308 private LayoutInflater mInflater;
309
310 @Thunk
311 Workspace mWorkspace;
312
313 private View mLauncherView;
314
315 private View mPageIndicators;
316
317 @Thunk
318 DragLayer mDragLayer;
319
320 private DragController mDragController;
321
322 private View mWeightWatcher;
323
324 private AppWidgetManagerCompat mAppWidgetManager;
325
326 private LauncherAppWidgetHost mAppWidgetHost;
327
328 @Thunk
329 ItemInfo mPendingAddInfo = new ItemInfo();
330
331 private LauncherAppWidgetProviderInfo mPendingAddWidgetInfo;
332
333 private int mPendingAddWidgetId = -1;
334
335 private int[] mTmpAddItemCellCoordinates = new int[2];
336
337 private FolderInfo mFolderInfo;
338
339 private Hotseat mHotseat;
340
341 private ViewGroup mOverviewPanel;
342
343 private View mAllAppsButton;
344
345 private SearchDropTargetBar mSearchDropTargetBar;
346
347 // Main container view for the all apps screen.
348 @Thunk
349 AppsContainerView mAppsView;
350
351 // Main container view for the widget tray screen.
352 private WidgetsContainerView mWidgetsView;
353
354 private boolean mAutoAdvanceRunning = false;
355
356 private AppWidgetHostView mQsb;
357
358 private Bundle mSavedState;
359
360 // We set the state in both onCreate and then onNewIntent in some cases, which causes both
361 // scroll issues (because the workspace may not have been measured yet) and extra work.
362 // Instead, just save the state that we need to restore Launcher to, and commit it in onResume.
363 private State mOnResumeState = State.NONE;
364
365 private SpannableStringBuilder mDefaultKeySsb = null;
366
367 @Thunk
368 boolean mWorkspaceLoading = true;
369
370 private boolean mPaused = true;
371
372 private boolean mRestoring;
373
374 private boolean mWaitingForResult;
375
376 private boolean mOnResumeNeedsLoad;
377
378 private ArrayList<Runnable> mBindOnResumeCallbacks = new ArrayList<Runnable>();
379
380 private ArrayList<Runnable> mOnResumeCallbacks = new ArrayList<Runnable>();
381
382 private Bundle mSavedInstanceState;
383
384 private LauncherModel mModel;
385
386 private IconCache mIconCache;
387
388 @Thunk
389 boolean mUserPresent = true;
390
391 private boolean mVisible = false;
392
393 private boolean mHasFocus = false;
394
395 private boolean mAttached = false;
396
397 @Thunk
398 static LocaleConfiguration sLocaleConfiguration = null;
399
400 private static HashMap<Long, FolderInfo> sFolders = new HashMap<Long, FolderInfo>();
401
402 private View.OnTouchListener mHapticFeedbackTouchListener;
403
404 // Related to the auto-advancing of widgets
405 // Related to the auto-advancing of widgets
406 private final int ADVANCE_MSG = 1;
407
408 private final int mAdvanceInterval = 20000;
409
410 private final int mAdvanceStagger = 250;
411
412 private long mAutoAdvanceSentTime;
413
414 private long mAutoAdvanceTimeLeft = -1;
415
416 @Thunk
417 HashMap<View, AppWidgetProviderInfo> mWidgetsToAdvance = new HashMap<View, AppWidgetProviderInfo>();
418
419 // Determines how long to wait after a rotation before restoring the screen orientation to
420 // match the sensor state.
421 // Determines how long to wait after a rotation before restoring the screen orientation to
422 // match the sensor state.
423 private final int mRestoreScreenOrientationDelay = 500;
424
425 @Thunk
426 Drawable mWorkspaceBackgroundDrawable;
427
428 private final ArrayList<Integer> mSynchronouslyBoundPages = new ArrayList<Integer>();
429
430 private static final boolean DISABLE_SYNCHRONOUS_BINDING_CURRENT_PAGE = false;
431
432 static final ArrayList<String> sDumpLogs = new ArrayList<String>();
433
434 static Date sDateStamp = new Date();
435
436 static DateFormat sDateFormat = DateFormat.getDateTimeInstance(DateFormat.SHORT, DateFormat.SHORT);
437
438 static long sRunStart = System.currentTimeMillis();
439
440 static final String CORRUPTION_EMAIL_SENT_KEY = "corruptionEmailSent";
441
442 // We only want to get the SharedPreferences once since it does an FS stat each time we get
443 // it from the context.
444 // We only want to get the SharedPreferences once since it does an FS stat each time we get
445 // it from the context.
446 private SharedPreferences mSharedPrefs;
447
448 // Holds the page that we need to animate to, and the icon views that we need to animate up
449 // when we scroll to that page on resume.
450 @Thunk
451 ImageView mFolderIconImageView;
452
453 private Bitmap mFolderIconBitmap;
454
455 private Canvas mFolderIconCanvas;
456
457 private Rect mRectForFolderAnimation = new Rect();
458
459 private BubbleTextView mWaitingForResume;
460
461 protected static HashMap<String, CustomAppWidget> sCustomAppWidgets = new HashMap<String, CustomAppWi🔵
462
463 private static final boolean ENABLE_CUSTOM_WIDGET_TEST = false;
464
465 static {
466 if (ENABLE_CUSTOM_WIDGET_TEST) {
467 sCustomAppWidgets.put(DummyWidget.class.getName(), new DummyWidget());
468 }
469 }
470
471 // TODO: remove this field and call method directly when Launcher3 can depend on M APIs
472 // TODO: remove this field and call method directly when Launcher3 can depend on M APIs
473 private static Method sClipRevealMethod = null;
474
475 static {
476 Class<?> activityOptionsClass = ActivityOptions.class;
477 try {
478 sClipRevealMethod = activityOptionsClass.getDeclaredMethod("makeClipRevealAnimation", View.cl🔵
479 } catch (java.lang.Exception e) {
480 // Earlier version
481 }
482 }
483
484 @Thunk
485 Runnable mBuildLayersRunnable = new Runnable() {
486 public void run() {
487 if (mWorkspace != null) {
488 mWorkspace.buildPageHardwareLayers();
489 }
490 }
491 };
492
493 private static PendingAddArguments sPendingAddItem;
494
495 @Thunk
496 static class PendingAddArguments {
497 int requestCode;
498
499 Intent intent;
500
501 long container;
502
503 long screenId;
504
505 int cellX;
506
507 int cellY;
508
509 int appWidgetId;
510 }
511
512 private Stats mStats;
513
514 FocusIndicatorView mFocusHandler;
515
516 @Override
517 protected void onCreate(Bundle savedInstanceState) {
518 if (DEBUG_STRICT_MODE) {
519 StrictMode.setThreadPolicy(// or .detectAll() for all detectable problems
520 new StrictMode.ThreadPolicy.Builder().detectDiskReads().detectDiskWrites().detectNetwork().pe🔵
521 StrictMode.setVmPolicy(new StrictMode.VmPolicy.Builder().detectLeakedSqlLiteObjects().detectL🔵
522 }
523 if (mLauncherCallbacks != null) {
524 mLauncherCallbacks.preOnCreate();
525 }
526 super.onCreate(savedInstanceState);
527 LauncherAppState.setApplicationContext(getApplicationContext());
528 LauncherAppState app = LauncherAppState.getInstance();
529 LauncherAppState.getLauncherProvider().setLauncherProviderChangeListener(this);
530 // Lazy-initialize the dynamic grid
531 DeviceProfile grid = app.initDynamicGrid(this);
532 // the LauncherApplication should call this, but in case of Instrumentation it might not be prese🔵
533 mSharedPrefs = getSharedPreferences(LauncherAppState.getSharedPreferencesKey(), Context.MODE_PRIV🔵
534 mIsSafeModeEnabled = getPackageManager().isSafeMode();
535 mModel = app.setLauncher(this);
536 mIconCache = app.getIconCache();
537 mIconCache.flushInvalidIcons(grid);
538 mDragController = new DragController(this);
539 mInflater = getLayoutInflater();
540 mStateTransitionAnimation = new LauncherStateTransitionAnimation(this, this);
541 mStats = new Stats(this);
542 mAppWidgetManager = AppWidgetManagerCompat.getInstance(this);
543 mAppWidgetHost = new LauncherAppWidgetHost(this, APPWIDGET_HOST_ID);
544 mAppWidgetHost.startListening();
545 // If we are getting an onCreate, we can actually preempt onResume and unset mPaused here,
546 // this also ensures that any synchronous binding below doesn't re-trigger another
547 // LauncherModel load.
548 mPaused = false;
549 if (PROFILE_STARTUP) {
550 android.os.Debug.startMethodTracing(Environment.getExternalStorageDirectory() + "/launcher");
551 }
552 checkForLocaleChange();
553 setContentView(R.layout.launcher);
554 setupViews();
555 grid.layout(this);
556 registerContentObservers();
557 lockAllApps();
558 mSavedState = savedInstanceState;
559 restoreState(mSavedState);
560 if (PROFILE_STARTUP) {
561 android.os.Debug.stopMethodTracing();
562 }
563 if (!mRestoring) {
564 if (DISABLE_SYNCHRONOUS_BINDING_CURRENT_PAGE) {
565 // If the user leaves launcher, then we should just load items asynchronously when
566 // they return.
567 mModel.startLoader(true, PagedView.INVALID_RESTORE_PAGE);
568 } else {
569 // We only load the page synchronously if the user rotates (or triggers a
570 // configuration change) while launcher is in the foreground
571 mModel.startLoader(true, mWorkspace.getRestorePage());
572 }
573 }
574 // For handling default keys
575 mDefaultKeySsb = new SpannableStringBuilder();
576 Selection.setSelection(mDefaultKeySsb, 0);
577 IntentFilter filter = new IntentFilter(Intent.ACTION_CLOSE_SYSTEM_DIALOGS);
578 registerReceiver(mCloseSystemDialogsReceiver, filter);
579 // On large interfaces, we want the screen to auto-rotate based on the current orientation
580 unlockScreenOrientation(true);
581 if (mLauncherCallbacks != null) {
582 mLauncherCallbacks.onCreate(savedInstanceState);
583 if (mLauncherCallbacks.hasLauncherOverlay()) {
584 ViewStub stub = ((ViewStub) (findViewById(R.id.launcher_overlay_stub)));
585 mLauncherOverlayContainer = ((InsettableFrameLayout) (stub.inflate()));
586 mLauncherOverlay = mLauncherCallbacks.setLauncherOverlayView(mLauncherOverlayContainer, m🔵
587 mWorkspace.setLauncherOverlay(mLauncherOverlay);
588 }
589 }
590 if (shouldShowIntroScreen()) {
591 showIntroScreen();
592 } else {
593 showFirstRunActivity();
594 showFirstRunClings();
595 }
596 }
597
598 private LauncherCallbacks mLauncherCallbacks;
599
600 public void onPostCreate(Bundle savedInstanceState) {
601 super.onPostCreate(savedInstanceState);
602 if (mLauncherCallbacks != null) {
603 mLauncherCallbacks.onPostCreate(savedInstanceState);
604 }
605 }
606
607 public boolean setLauncherCallbacks(LauncherCallbacks callbacks) {
608 mLauncherCallbacks = callbacks;
609 mLauncherCallbacks.setLauncherAppsCallback(new Launcher.LauncherAppsCallbacks() {
610 @Override
611 public void onAllAppsBoundsChanged(Rect bounds) {
612 mAppsView.setFixedBounds(Launcher.this, bounds);
613 }
614
615 @Override
616 public void dismissAllApps() {
617 showWorkspace(true);
618 }
619 });
620 return true;
621 }
622
623 @Override
624 public void onLauncherProviderChange() {
625 if (mLauncherCallbacks != null) {
626 mLauncherCallbacks.onLauncherProviderChange();
627 }
628 }
629
630 /** To be overridden by subclasses to hint to Launcher that we have custom content */
631 protected boolean hasCustomContentToLeft() {
632 if (mLauncherCallbacks != null) {
633 return mLauncherCallbacks.hasCustomContentToLeft();
634 }
635 return false;
636 }
637
638 /**
639 * To be overridden by subclasses to populate the custom content container and call
640 * {@link #addToCustomContentPage}. This will only be invoked if
641 * {@link #hasCustomContentToLeft()} is {@code true}.
642 */
643 protected void populateCustomContentContainer() {
644 if (mLauncherCallbacks != null) {
645 mLauncherCallbacks.populateCustomContentContainer();
646 }
647 }
648
649 /**
650 * Invoked by subclasses to signal a change to the {@link #addCustomContentToLeft} value to
651 * ensure the custom content page is added or removed if necessary.
652 */
653 protected void invalidateHasCustomContentToLeft() {
654 if (mWorkspace == null || mWorkspace.getScreenOrder().isEmpty()) {
655 // Not bound yet, wait for bindScreens to be called.
656 return;
657 }
658
659 if (!mWorkspace.hasCustomContent() && hasCustomContentToLeft()) {
660 // Create the custom content page and call the subclass to populate it.
661 mWorkspace.createCustomContentContainer();
662 populateCustomContentContainer();
663 } else if (mWorkspace.hasCustomContent() && !hasCustomContentToLeft()) {
664 mWorkspace.removeCustomContentPage();
665 }
666 }
667
668 @Thunk
669 void checkForLocaleChange() {
670 if (sLocaleConfiguration == null) {
671 new AsyncTask<Void, Void, LocaleConfiguration>() {
672 @Override
673 protected LocaleConfiguration doInBackground(Void... unused) {
674 LocaleConfiguration localeConfiguration = new LocaleConfiguration();
675 readConfiguration(Launcher.this, localeConfiguration);
676 return localeConfiguration;
677 }
678
679 @Override
680 protected void onPostExecute(LocaleConfiguration result) {
681 sLocaleConfiguration = result;
682 checkForLocaleChange();// recursive, but now with a locale configuration
683
684 }
685 }.execute();
686 return;
687 }
688 final Configuration configuration = getResources().getConfiguration();
689 final String previousLocale = Launcher.sLocaleConfiguration.locale;
690 final String locale = configuration.locale.toString();
691 final int previousMcc = Launcher.sLocaleConfiguration.mcc;
692 final int mcc = configuration.mcc;
693 final int previousMnc = Launcher.sLocaleConfiguration.mnc;
694 final int mnc = configuration.mnc;
695 boolean localeChanged = ((!locale.equals(previousLocale)) || (mcc != previousMcc)) || (mnc != pre🔵
696 if (localeChanged) {
697 Launcher.sLocaleConfiguration.locale = locale;
698 Launcher.sLocaleConfiguration.mcc = mcc;
699 Launcher.sLocaleConfiguration.mnc = mnc;
700 mIconCache.flush();
701 final LocaleConfiguration localeConfiguration = sLocaleConfiguration;
702 new AsyncTask<Void, Void, Void>() {
703 public Void doInBackground(Void... args) {
704 writeConfiguration(Launcher.this, localeConfiguration);
705 return null;
706 }
707 }.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR, ((Void) (null)));
708 }
709 }
710
711 @Thunk
712 static class LocaleConfiguration {
713 public String locale;
714
715 public int mcc = -1;
716
717 public int mnc = -1;
718 }
719
720 @Thunk
721 static void readConfiguration(Context context, LocaleConfiguration configuration) {
722 DataInputStream in = null;
723 try {
724 in = new DataInputStream(context.openFileInput(LauncherFiles.LAUNCHER_PREFERENCES));
725 configuration.locale = in.readUTF();
726 configuration.mcc = in.readInt();
727 configuration.mnc = in.readInt();
728 } catch (FileNotFoundException e) {
729 // Ignore
730 } catch (IOException e) {
731 // Ignore
732 } finally {
733 if (in != null) {
734 try {
735 in.close();
736 } catch (IOException e) {
737 // Ignore
738 }
739 }
740 }
741 }
742
743 @Thunk
744 static void writeConfiguration(Context context, LocaleConfiguration configuration) {
745 DataOutputStream out = null;
746 try {
747 out = new DataOutputStream(context.openFileOutput(LauncherFiles.LAUNCHER_PREFERENCES, MODE_PR🔵
748 out.writeUTF(configuration.locale);
749 out.writeInt(configuration.mcc);
750 out.writeInt(configuration.mnc);
751 out.flush();
752 } catch (FileNotFoundException e) {
753 // Ignore
754 } catch (IOException e) {
755 // noinspection ResultOfMethodCallIgnored
756 context.getFileStreamPath(LauncherFiles.LAUNCHER_PREFERENCES).delete();
757 } finally {
758 if (out != null) {
759 try {
760 out.close();
761 } catch (IOException e) {
762 // Ignore
763 }
764 }
765 }
766 }
767
768 public Stats getStats() {
769 return mStats;
770 }
771
772 public LayoutInflater getInflater() {
773 return mInflater;
774 }
775
776 public boolean isDraggingEnabled() {
777 // We prevent dragging when we are loading the workspace as it is possible to pick up a view
778 // that is subsequently removed from the workspace in startBinding().
779 return !mModel.isLoadingWorkspace();
780 }
781
782 @TargetApi(Build.VERSION_CODES.JELLY_BEAN_MR1)
783 public static int generateViewId() {
784 if (Build.VERSION.SDK_INT >= 17) {
785 return View.generateViewId();
786 } else {
787 // View.generateViewId() is not available. The following fallback logic is a copy
788 // of its implementation.
789 for (;;) {
790 final int result = sNextGeneratedId.get();
791 // aapt-generated IDs have the high byte nonzero; clamp to the range under that.
792 int newValue = result + 1;
793 if (newValue > 0x00FFFFFF) newValue = 1; // Roll over to 1, not 0.
794 if (sNextGeneratedId.compareAndSet(result, newValue)) {
795 return result;
796 }
797 }
798 }
799 }
800
801 public int getViewIdForItem(ItemInfo info) {
802 // This cast is safe given the > 2B range for int.
803 int itemId = (int) info.id;
804 if (mItemIdToViewId.containsKey(itemId)) {
805 return mItemIdToViewId.get(itemId);
806 }
807 int viewId = generateViewId();
808 mItemIdToViewId.put(itemId, viewId);
809 return viewId;
810 }
811
812 /**
813 * Returns whether we should delay spring loaded mode -- for shortcuts and widgets that have
814 * a configuration step, this allows the proper animations to run after other transitions.
815 */
816 private long completeAdd(PendingAddArguments args) {
817 long screenId = args.screenId;
818 if (args.container == LauncherSettings.Favorites.CONTAINER_DESKTOP) {
819 // When the screen id represents an actual screen (as opposed to a rank) we make sure
820 // that the drop page actually exists.
821 screenId = ensurePendingDropLayoutExists(args.screenId);
822 }
823
824 switch (args.requestCode) {
825 case REQUEST_CREATE_SHORTCUT:
826 completeAddShortcut(args.intent, args.container, screenId, args.cellX,
827 args.cellY);
828 break;
829 case REQUEST_CREATE_APPWIDGET:
830 completeAddAppWidget(args.appWidgetId, args.container, screenId, null, null);
831 break;
832 case REQUEST_RECONFIGURE_APPWIDGET:
833 completeRestoreAppWidget(args.appWidgetId);
834 break;
835 }
836 // Before adding this resetAddInfo(), after a shortcut was added to a workspace screen,
837 // if you turned the screen off and then back while in All Apps, Launcher would not
838 // return to the workspace. Clearing mAddInfo.container here fixes this issue
839 resetAddInfo();
840 return screenId;
841 }
842
843 private void handleActivityResult(
844 final int requestCode, final int resultCode, final Intent data) {
845 // Reset the startActivity waiting flag
846 setWaitingForResult(false);
847 final int pendingAddWidgetId = mPendingAddWidgetId;
848 mPendingAddWidgetId = -1;
849
850 Runnable exitSpringLoaded = new Runnable() {
851 @Override
852 public void run() {
853 exitSpringLoadedDragModeDelayed((resultCode != RESULT_CANCELED),
854 EXIT_SPRINGLOADED_MODE_SHORT_TIMEOUT, null);
855 }
856 };
857
858 if (requestCode == REQUEST_BIND_APPWIDGET) {
859 final int appWidgetId = data != null ?
860 data.getIntExtra(AppWidgetManager.EXTRA_APPWIDGET_ID, -1) : -1;
861 if (resultCode == RESULT_CANCELED) {
862 completeTwoStageWidgetDrop(RESULT_CANCELED, appWidgetId);
863 mWorkspace.removeExtraEmptyScreenDelayed(true, exitSpringLoaded,
864 ON_ACTIVITY_RESULT_ANIMATION_DELAY, false);
865 } else if (resultCode == RESULT_OK) {
866 addAppWidgetImpl(appWidgetId, mPendingAddInfo, null,
867 mPendingAddWidgetInfo, ON_ACTIVITY_RESULT_ANIMATION_DELAY);
868 }
869 return;
870 } else if (requestCode == REQUEST_PICK_WALLPAPER) {
871 if (resultCode == RESULT_OK && mWorkspace.isInOverviewMode()) {
872 mWorkspace.exitOverviewMode(false);
873 }
874 return;
875 }
876
877 boolean isWidgetDrop = (requestCode == REQUEST_PICK_APPWIDGET ||
878 requestCode == REQUEST_CREATE_APPWIDGET);
879
880 final boolean workspaceLocked = isWorkspaceLocked();
881 // We have special handling for widgets
882 if (isWidgetDrop) {
883 final int appWidgetId;
884 int widgetId = data != null ? data.getIntExtra(AppWidgetManager.EXTRA_APPWIDGET_ID, -1)
885 : -1;
886 if (widgetId < 0) {
887 appWidgetId = pendingAddWidgetId;
888 } else {
889 appWidgetId = widgetId;
890 }
891
892 final int result;
893 if (appWidgetId < 0 || resultCode == RESULT_CANCELED) {
894 Log.e(TAG, "Error: appWidgetId (EXTRA_APPWIDGET_ID) was not " +
895 "returned from the widget configuration activity.");
896 result = RESULT_CANCELED;
897 completeTwoStageWidgetDrop(result, appWidgetId);
898 final Runnable onComplete = new Runnable() {
899 @Override
900 public void run() {
901 exitSpringLoadedDragModeDelayed(false, 0, null);
902 }
903 };
904 if (workspaceLocked) {
905 // No need to remove the empty screen if we're mid-binding, as the
906 // the bind will not add the empty screen.
907 mWorkspace.postDelayed(onComplete, ON_ACTIVITY_RESULT_ANIMATION_DELAY);
908 } else {
909 mWorkspace.removeExtraEmptyScreenDelayed(true, onComplete,
910 ON_ACTIVITY_RESULT_ANIMATION_DELAY, false);
911 }
912 } else {
913 if (!workspaceLocked) {
914 if (mPendingAddInfo.container == LauncherSettings.Favorites.CONTAINER_DESKTOP) {
915 // When the screen id represents an actual screen (as opposed to a rank)
916 // we make sure that the drop page actually exists.
917 mPendingAddInfo.screenId =
918 ensurePendingDropLayoutExists(mPendingAddInfo.screenId);
919 }
920 final CellLayout dropLayout = mWorkspace.getScreenWithId(mPendingAddInfo.screenId);
921
922 dropLayout.setDropPending(true);
923 final Runnable onComplete = new Runnable() {
924 @Override
925 public void run() {
926 completeTwoStageWidgetDrop(resultCode, appWidgetId);
927 dropLayout.setDropPending(false);
928 }
929 };
930 mWorkspace.removeExtraEmptyScreenDelayed(true, onComplete,
931 ON_ACTIVITY_RESULT_ANIMATION_DELAY, false);
932 } else {
933 PendingAddArguments args = preparePendingAddArgs(requestCode, data, appWidgetId,
934 mPendingAddInfo);
935 sPendingAddItem = args;
936 }
937 }
938 return;
939 }
940
941 if (requestCode == REQUEST_RECONFIGURE_APPWIDGET) {
942 if (resultCode == RESULT_OK) {
943 // Update the widget view.
944 PendingAddArguments args = preparePendingAddArgs(requestCode, data,
945 pendingAddWidgetId, mPendingAddInfo);
946 if (workspaceLocked) {
947 sPendingAddItem = args;
948 } else {
949 completeAdd(args);
950 }
951 }
952 // Leave the widget in the pending state if the user canceled the configure.
953 return;
954 }
955
956 // The pattern used here is that a user PICKs a specific application,
957 // which, depending on the target, might need to CREATE the actual target.
958
959 // For example, the user would PICK_SHORTCUT for "Music playlist", and we
960 // launch over to the Music app to actually CREATE_SHORTCUT.
961 if (resultCode == RESULT_OK && mPendingAddInfo.container != ItemInfo.NO_ID) {
962 final PendingAddArguments args = preparePendingAddArgs(requestCode, data, -1,
963 mPendingAddInfo);
964 if (isWorkspaceLocked()) {
965 sPendingAddItem = args;
966 } else {
967 completeAdd(args);
968 mWorkspace.removeExtraEmptyScreenDelayed(true, exitSpringLoaded,
969 ON_ACTIVITY_RESULT_ANIMATION_DELAY, false);
970 }
971 } else if (resultCode == RESULT_CANCELED) {
972 mWorkspace.removeExtraEmptyScreenDelayed(true, exitSpringLoaded,
973 ON_ACTIVITY_RESULT_ANIMATION_DELAY, false);
974 }
975 mDragLayer.clearAnimatedView();
976
977 }
978
979 @Override
980 protected void onActivityResult(
981 final int requestCode, final int resultCode, final Intent data) {
982 handleActivityResult(requestCode, resultCode, data);
983 if (mLauncherCallbacks != null) {
984 mLauncherCallbacks.onActivityResult(requestCode, resultCode, data);
985 }
986 }
987
988 private PendingAddArguments preparePendingAddArgs(int requestCode, Intent data, int
989 appWidgetId, ItemInfo info) {
990 PendingAddArguments args = new PendingAddArguments();
991 args.requestCode = requestCode;
992 args.intent = data;
993 args.container = info.container;
994 args.screenId = info.screenId;
995 args.cellX = info.cellX;
996 args.cellY = info.cellY;
997 args.appWidgetId = appWidgetId;
998 return args;
999 }
1000
1001 /**
1002 * Check to see if a given screen id exists. If not, create it at the end, return the new id.
1003 *
1004 * @param screenId the screen id to check
1005 * @return the new screen, or screenId if it exists
1006 */
1007 private long ensurePendingDropLayoutExists(long screenId) {
1008 CellLayout dropLayout =
1009 (CellLayout) mWorkspace.getScreenWithId(screenId);
1010 if (dropLayout == null) {
1011 // it's possible that the add screen was removed because it was
1012 // empty and a re-bind occurred
1013 mWorkspace.addExtraEmptyScreen();
1014 return mWorkspace.commitExtraEmptyScreen();
1015 } else {
1016 return screenId;
1017 }
1018 }
1019
1020 @Thunk
1021 void completeTwoStageWidgetDrop(final int resultCode, final int appWidgetId) {
1022 CellLayout cellLayout = ((CellLayout) (mWorkspace.getScreenWithId(mPendingAddInfo.screenId)));
1023 Runnable onCompleteRunnable = null;
1024 int animationType = 0;
1025 AppWidgetHostView boundWidget = null;
1026 if (resultCode == RESULT_OK) {
1027 animationType = Workspace.COMPLETE_TWO_STAGE_WIDGET_DROP_ANIMATION;
1028 final AppWidgetHostView layout = mAppWidgetHost.createView(this, appWidgetId, mPendingAddWidg🔵
1029 boundWidget = layout;
1030 onCompleteRunnable = new Runnable() {
1031 @Override
1032 public void run() {
1033 completeAddAppWidget(appWidgetId, mPendingAddInfo.container, mPendingAddInfo.screenId🔵
1034 exitSpringLoadedDragModeDelayed(resultCode != RESULT_CANCELED, EXIT_SPRINGLOADED_MODE🔵
1035 }
1036 };
1037 } else if (resultCode == RESULT_CANCELED) {
1038 mAppWidgetHost.deleteAppWidgetId(appWidgetId);
1039 animationType = Workspace.CANCEL_TWO_STAGE_WIDGET_DROP_ANIMATION;
1040 }
1041 if (mDragLayer.getAnimatedView() != null) {
1042 mWorkspace.animateWidgetDrop(mPendingAddInfo, cellLayout, ((DragView) (mDragLayer.getAnimated🔵
1043 } else if (onCompleteRunnable != null) {
1044 // The animated view may be null in the case of a rotation during widget configuration
1045 onCompleteRunnable.run();
1046 }
1047 }
1048
1049 @Override
1050 protected void onStop() {
1051 super.onStop();
1052 FirstFrameAnimatorHelper.setIsVisible(false);
1053
1054 if (mLauncherCallbacks != null) {
1055 mLauncherCallbacks.onStop();
1056 }
1057 }
1058
1059 @Override
1060 protected void onStart() {
1061 super.onStart();
1062 FirstFrameAnimatorHelper.setIsVisible(true);
1063
1064 if (mLauncherCallbacks != null) {
1065 mLauncherCallbacks.onStart();
1066 }
1067 }
1068
1069 @Override
1070 protected void onResume() {
1071 long startTime = 0;
1072 if (DEBUG_RESUME_TIME) {
1073 startTime = System.currentTimeMillis();
1074 Log.v(TAG, "Launcher.onResume()");
1075 }
1076 if (mLauncherCallbacks != null) {
1077 mLauncherCallbacks.preOnResume();
1078 }
1079 super.onResume();
1080 // Restore the previous launcher state
1081 if (mOnResumeState == State.WORKSPACE) {
1082 showWorkspace(false);
1083 } else if (mOnResumeState == State.APPS) {
1084 /* animated */
1085 /* resetListToTop */
1086 showAppsView(false, false);
1087 } else if (mOnResumeState == State.WIDGETS) {
1088 showWidgetsView(false, false);
1089 }
1090 mOnResumeState = State.NONE;
1091 // Background was set to gradient in onPause(), restore to black if in all apps.
1092 setWorkspaceBackground(mState == State.WORKSPACE);
1093 mPaused = false;
1094 if (mRestoring || mOnResumeNeedsLoad) {
1095 setWorkspaceLoading(true);
1096 mModel.startLoader(true, PagedView.INVALID_RESTORE_PAGE);
1097 mRestoring = false;
1098 mOnResumeNeedsLoad = false;
1099 }
1100 if (mBindOnResumeCallbacks.size() > 0) {
1101 // We might have postponed some bind calls until onResume (see waitUntilResume) --
1102 // execute them here
1103 long startTimeCallbacks = 0;
1104 if (DEBUG_RESUME_TIME) {
1105 startTimeCallbacks = System.currentTimeMillis();
1106 }
1107 for (int i = 0; i < mBindOnResumeCallbacks.size(); i++) {
1108 mBindOnResumeCallbacks.get(i).run();
1109 }
1110 mBindOnResumeCallbacks.clear();
1111 if (DEBUG_RESUME_TIME) {
1112 Log.d(TAG, "Time spent processing callbacks in onResume: " + (System.currentTimeMillis() 🔵
1113 }
1114 }
1115 if (mOnResumeCallbacks.size() > 0) {
1116 for (int i = 0; i < mOnResumeCallbacks.size(); i++) {
1117 mOnResumeCallbacks.get(i).run();
1118 }
1119 mOnResumeCallbacks.clear();
1120 }
1121 // Reset the pressed state of icons that were locked in the press state while activities
1122 // were launching
1123 if (mWaitingForResume != null) {
1124 // Resets the previous workspace icon press state
1125 mWaitingForResume.setStayPressed(false);
1126 }
1127 // It is possible that widgets can receive updates while launcher is not in the foreground.
1128 // Consequently, the widgets will be inflated in the orientation of the foreground activity
1129 // (framework issue). On resuming, we ensure that any widgets are inflated for the current
1130 // orientation.
1131 getWorkspace().reinflateWidgetsIfNecessary();
1132 reinflateQSBIfNecessary();
1133 // Process any items that were added while Launcher was away.
1134 InstallShortcutReceiver.disableAndFlushInstallQueue(this);
1135 if (DEBUG_RESUME_TIME) {
1136 Log.d(TAG, "Time spent in onResume: " + (System.currentTimeMillis() - startTime));
1137 }
1138 if (mWorkspace.getCustomContentCallbacks() != null) {
1139 // If we are resuming and the custom content is the current page, we call onShow().
1140 // It is also poassible that onShow will instead be called slightly after first layout
1141 // if PagedView#setRestorePage was set to the custom content page in onCreate().
1142 if (mWorkspace.isOnOrMovingToCustomContent()) {
1143 mWorkspace.getCustomContentCallbacks().onShow(true);
1144 }
1145 }
1146 mWorkspace.updateInteractionForState();
1147 mWorkspace.onResume();
1148 PackageInstallerCompat.getInstance(this).onResume();
1149 if (mLauncherCallbacks != null) {
1150 mLauncherCallbacks.onResume();
1151 }
1152 }
1153
1154 @Override
1155 protected void onPause() {
1156 // Ensure that items added to Launcher are queued until Launcher returns
1157 InstallShortcutReceiver.enableInstallQueue();
1158 PackageInstallerCompat.getInstance(this).onPause();
1159
1160 super.onPause();
1161 mPaused = true;
1162 mDragController.cancelDrag();
1163 mDragController.resetLastGestureUpTime();
1164
1165 // We call onHide() aggressively. The custom content callbacks should be able to
1166 // debounce excess onHide calls.
1167 if (mWorkspace.getCustomContentCallbacks() != null) {
1168 mWorkspace.getCustomContentCallbacks().onHide();
1169 }
1170
1171 if (mLauncherCallbacks != null) {
1172 mLauncherCallbacks.onPause();
1173 }
1174 }
1175
1176 public interface CustomContentCallbacks {
1177 // Custom content is completely shown. {@code fromResume} indicates whether this was caused
1178 // by a onResume or by scrolling otherwise.
1179 public abstract void onShow(boolean fromResume);
1180
1181 // Custom content is completely hidden
1182 public abstract void onHide();
1183
1184 // Custom content scroll progress changed. From 0 (not showing) to 1 (fully showing).
1185 public abstract void onScrollProgressChanged(float progress);
1186
1187 // Indicates whether the user is allowed to scroll away from the custom content.
1188 public abstract boolean isScrollingAllowed();
1189 }
1190
1191 public interface LauncherOverlay {
1192 /**
1193 * Touch interaction leading to overscroll has begun
1194 */
1195 public abstract void onScrollInteractionBegin();
1196
1197 /**
1198 * Touch interaction related to overscroll has ended
1199 */
1200 public abstract void onScrollInteractionEnd();
1201
1202 /**
1203 * Scroll progress, between 0 and 100, when the user scrolls beyond the leftmost
1204 * screen (or in the case of RTL, the rightmost screen).
1205 */
1206 public abstract void onScrollChange(int progress, boolean rtl);
1207
1208 /**
1209 * Screen has stopped scrolling
1210 */
1211 public abstract void onScrollSettled();
1212
1213 /**
1214 * This method can be called by the Launcher in order to force the LauncherOverlay
1215 * to exit fully immersive mode.
1216 */
1217 public abstract void forceExitFullImmersion();
1218 }
1219
1220 public interface LauncherAppsCallbacks {
1221 /**
1222 * Updates launcher to the available space that AllApps can take so as not to overlap with
1223 * any other views.
1224 */
1225 public abstract void onAllAppsBoundsChanged(Rect bounds);
1226
1227 /**
1228 * Called to dismiss all apps if it is showing.
1229 */
1230 public abstract void dismissAllApps();
1231 }
1232
1233 public interface LauncherOverlayCallbacks {
1234 /**
1235 * This method indicates whether a call to {@link #enterFullImmersion()} will succeed,
1236 * however it doesn't modify any state within the launcher.
1237 */
1238 public abstract boolean canEnterFullImmersion();
1239
1240 /**
1241 * Should be called to tell Launcher that the LauncherOverlay will take over interaction,
1242 * eg. by occupying the full screen and handling all touch events.
1243 *
1244 * @return true if Launcher allows the LauncherOverlay to become fully immersive. In this
1245 case, Launcher will modify any necessary state and assumes the overlay is
1246 handling all interaction. If false, the LauncherOverlay should cancel any
1247 */
1248 public abstract boolean enterFullImmersion();
1249
1250 /**
1251 * Must be called when exiting fully immersive mode. Indicates to Launcher that it has
1252 * full control over UI and state.
1253 */
1254 public abstract void exitFullImmersion();
1255 }
1256
1257 class LauncherOverlayCallbacksImpl implements LauncherOverlayCallbacks {
1258 @Override
1259 public boolean canEnterFullImmersion() {
1260 return mState == State.WORKSPACE;
1261 }
1262
1263 @Override
1264 public boolean enterFullImmersion() {
1265 if (mState == State.WORKSPACE) {
1266 // When fully immersed, disregard any touches which fall through.
1267 mDragLayer.setBlockTouch(true);
1268 return true;
1269 }
1270 return false;
1271 }
1272
1273 @Override
1274 public void exitFullImmersion() {
1275 mDragLayer.setBlockTouch(false);
1276 }
1277 }
1278
1279 protected boolean hasSettings() {
1280 if (mLauncherCallbacks != null) {
1281 return mLauncherCallbacks.hasSettings();
1282 }
1283 return false;
1284 }
1285
1286 public void addToCustomContentPage(View customContent,
1287 CustomContentCallbacks callbacks, String description) {
1288 mWorkspace.addToCustomContentPage(customContent, callbacks, description);
1289 }
1290
1291 // The custom content needs to offset its content to account for the QSB
1292 public int getTopOffsetForCustomContent() {
1293 return mWorkspace.getPaddingTop();
1294 }
1295
1296 @Override
1297 public Object onRetainNonConfigurationInstance() {
1298 // Flag the loader to stop early before switching
1299 if (mModel.isCurrentCallbacks(this)) {
1300 mModel.stopLoader();
1301 }
1302 //TODO(hyunyoungs): stop the widgets loader when there is a rotation.
1303 return Boolean.TRUE;
1304 }
1305
1306 // We can't hide the IME if it was forced open. So don't bother
1307 @Override
1308 public void onWindowFocusChanged(boolean hasFocus) {
1309 super.onWindowFocusChanged(hasFocus);
1310 mHasFocus = hasFocus;
1311
1312 if (mLauncherCallbacks != null) {
1313 mLauncherCallbacks.onWindowFocusChanged(hasFocus);
1314 }
1315 }
1316
1317 private boolean acceptFilter() {
1318 final InputMethodManager inputManager = (InputMethodManager)
1319 getSystemService(Context.INPUT_METHOD_SERVICE);
1320 return !inputManager.isFullscreenMode();
1321 }
1322
1323 @Override
1324 public boolean onKeyDown(int keyCode, KeyEvent event) {
1325 final int uniChar = event.getUnicodeChar();
1326 final boolean handled = super.onKeyDown(keyCode, event);
1327 final boolean isKeyNotWhitespace = uniChar > 0 && !Character.isWhitespace(uniChar);
1328 if (!handled && acceptFilter() && isKeyNotWhitespace) {
1329 boolean gotKey = TextKeyListener.getInstance().onKeyDown(mWorkspace, mDefaultKeySsb,
1330 keyCode, event);
1331 if (gotKey && mDefaultKeySsb != null && mDefaultKeySsb.length() > 0) {
1332 // something usable has been typed - start a search
1333 // the typed text will be retrieved and cleared by
1334 // showSearchDialog()
1335 // If there are multiple keystrokes before the search dialog takes focus,
1336 // onSearchRequested() will be called for every keystroke,
1337 // but it is idempotent, so it's fine.
1338 return onSearchRequested();
1339 }
1340 }
1341
1342 // Eat the long press event so the keyboard doesn't come up.
1343 if (keyCode == KeyEvent.KEYCODE_MENU && event.isLongPress()) {
1344 return true;
1345 }
1346
1347 return handled;
1348 }
1349
1350 private String getTypedText() {
1351 return mDefaultKeySsb.toString();
1352 }
1353
1354 private void clearTypedText() {
1355 mDefaultKeySsb.clear();
1356 mDefaultKeySsb.clearSpans();
1357 Selection.setSelection(mDefaultKeySsb, 0);
1358 }
1359
1360 /**
1361 * Given the integer (ordinal) value of a State enum instance, convert it to a variable of type
1362 * State
1363 */
1364 private static State intToState(int stateOrdinal) {
1365 State state = State.WORKSPACE;
1366 final State[] stateValues = State.values();
1367 for (int i = 0; i < stateValues.length; i++) {
1368 if (stateValues[i].ordinal() == stateOrdinal) {
1369 state = stateValues[i];
1370 break;
1371 }
1372 }
1373 return state;
1374 }
1375
1376 /**
1377 * Restores the previous state, if it exists.
1378 *
1379 * @param savedState The previous state.
1380 */
1381 @SuppressWarnings("unchecked")
1382 private void restoreState(Bundle savedState) {
1383 if (savedState == null) {
1384 return;
1385 }
1386 State state = intToState(savedState.getInt(RUNTIME_STATE, State.WORKSPACE.ordinal()));
1387 if ((state == State.APPS) || (state == State.WIDGETS)) {
1388 mOnResumeState = state;
1389 }
1390 int currentScreen = savedState.getInt(RUNTIME_STATE_CURRENT_SCREEN, PagedView.INVALID_RESTORE_PAG🔵
1391 if (currentScreen != PagedView.INVALID_RESTORE_PAGE) {
1392 mWorkspace.setRestorePage(currentScreen);
1393 }
1394 final long pendingAddContainer = savedState.getLong(RUNTIME_STATE_PENDING_ADD_CONTAINER, -1);
1395 final long pendingAddScreen = savedState.getLong(RUNTIME_STATE_PENDING_ADD_SCREEN, -1);
1396 if ((pendingAddContainer != ItemInfo.NO_ID) && (pendingAddScreen > (-1))) {
1397 mPendingAddInfo.container = pendingAddContainer;
1398 mPendingAddInfo.screenId = pendingAddScreen;
1399 mPendingAddInfo.cellX = savedState.getInt(RUNTIME_STATE_PENDING_ADD_CELL_X);
1400 mPendingAddInfo.cellY = savedState.getInt(RUNTIME_STATE_PENDING_ADD_CELL_Y);
1401 mPendingAddInfo.spanX = savedState.getInt(RUNTIME_STATE_PENDING_ADD_SPAN_X);
1402 mPendingAddInfo.spanY = savedState.getInt(RUNTIME_STATE_PENDING_ADD_SPAN_Y);
1403 AppWidgetProviderInfo info = savedState.getParcelable(RUNTIME_STATE_PENDING_ADD_WIDGET_INFO);
1404 mPendingAddWidgetInfo = LauncherAppWidgetProviderInfo.fromProviderInfo(this, info);
1405 mPendingAddWidgetId = savedState.getInt(RUNTIME_STATE_PENDING_ADD_WIDGET_ID);
1406 setWaitingForResult(true);
1407 mRestoring = true;
1408 }
1409 boolean renameFolder = savedState.getBoolean(RUNTIME_STATE_PENDING_FOLDER_RENAME, false);
1410 if (renameFolder) {
1411 long id = savedState.getLong(RUNTIME_STATE_PENDING_FOLDER_RENAME_ID);
1412 mFolderInfo = mModel.getFolderById(this, sFolders, id);
1413 mRestoring = true;
1414 }
1415 mItemIdToViewId = ((HashMap<Integer, Integer>) (savedState.getSerializable(RUNTIME_STATE_VIEW_IDS🔵
1416 }
1417
1418 /**
1419 * Finds all the views we need and configure them properly.
1420 */
1421 private void setupViews() {
1422 final DragController dragController = mDragController;
1423 mLauncherView = findViewById(R.id.launcher);
1424 mFocusHandler = ((FocusIndicatorView) (findViewById(R.id.focus_indicator)));
1425 mDragLayer = ((DragLayer) (findViewById(R.id.drag_layer)));
1426 mWorkspace = ((Workspace) (mDragLayer.findViewById(R.id.workspace)));
1427 mWorkspace.setPageSwitchListener(this);
1428 mPageIndicators = mDragLayer.findViewById(R.id.page_indicator);
1429 mLauncherView.setSystemUiVisibility(View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN | View.SYSTEM_UI_FLAG_L🔵
1430 mWorkspaceBackgroundDrawable = getResources().getDrawable(R.drawable.workspace_bg);
1431 // Setup the drag layer
1432 mDragLayer.setup(this, dragController);
1433 // Setup the hotseat
1434 mHotseat = ((Hotseat) (findViewById(R.id.hotseat)));
1435 if (mHotseat != null) {
1436 mHotseat.setup(this);
1437 mHotseat.setOnLongClickListener(this);
1438 }
1439 mOverviewPanel = ((ViewGroup) (findViewById(R.id.overview_panel)));
1440 View widgetButton = findViewById(R.id.widget_button);
1441 widgetButton.setOnClickListener(new OnClickListener() {
1442 @Override
1443 public void onClick(View arg0) {
1444 if (!mWorkspace.isSwitchingState()) {
1445 onClickAddWidgetButton(arg0);
1446 }
1447 }
1448 });
1449 widgetButton.setOnTouchListener(getHapticFeedbackTouchListener());
1450 View wallpaperButton = findViewById(R.id.wallpaper_button);
1451 wallpaperButton.setOnClickListener(new OnClickListener() {
1452 @Override
1453 public void onClick(View arg0) {
1454 if (!mWorkspace.isSwitchingState()) {
1455 onClickWallpaperPicker(arg0);
1456 }
1457 }
1458 });
1459 wallpaperButton.setOnTouchListener(getHapticFeedbackTouchListener());
1460 View settingsButton = findViewById(R.id.settings_button);
1461 if (hasSettings()) {
1462 settingsButton.setOnClickListener(new OnClickListener() {
1463 @Override
1464 public void onClick(View arg0) {
1465 if (!mWorkspace.isSwitchingState()) {
1466 onClickSettingsButton(arg0);
1467 }
1468 }
1469 });
1470 settingsButton.setOnTouchListener(getHapticFeedbackTouchListener());
1471 } else {
1472 settingsButton.setVisibility(View.GONE);
1473 }
1474 mOverviewPanel.setAlpha(0.0F);
1475 // Setup the workspace
1476 mWorkspace.setHapticFeedbackEnabled(false);
1477 mWorkspace.setOnLongClickListener(this);
1478 mWorkspace.setup(dragController);
1479 dragController.addDragListener(mWorkspace);
1480 // Get the search/delete bar
1481 mSearchDropTargetBar = ((SearchDropTargetBar) (mDragLayer.findViewById(R.id.search_drop_target_ba🔵
1482 // Setup Apps
1483 mAppsView = ((AppsContainerView) (findViewById(R.id.apps_view)));
1484 if ((mLauncherCallbacks != null) && mLauncherCallbacks.overrideAllAppsSearch()) {
1485 mAppsView.hideSearchBar();
1486 }
1487 // Setup AppsCustomize
1488 mWidgetsView = ((WidgetsContainerView) (findViewById(R.id.widgets_view)));
1489 // Setup the drag controller (drop targets have to be added in reverse order in priority)
1490 dragController.setDragScoller(mWorkspace);
1491 dragController.setScrollView(mDragLayer);
1492 dragController.setMoveTarget(mWorkspace);
1493 dragController.addDropTarget(mWorkspace);
1494 if (mSearchDropTargetBar != null) {
1495 mSearchDropTargetBar.setup(this, dragController);
1496 mSearchDropTargetBar.setQsbSearchBar(getOrCreateQsbBar());
1497 }
1498 if (getResources().getBoolean(R.bool.debug_memory_enabled)) {
1499 Log.v(TAG, "adding WeightWatcher");
1500 mWeightWatcher = new WeightWatcher(this);
1501 mWeightWatcher.setAlpha(0.5F);
1502 ((FrameLayout) (mLauncherView)).addView(mWeightWatcher, new FrameLayout.LayoutParams(FrameLay🔵
1503 boolean show = shouldShowWeightWatcher();
1504 mWeightWatcher.setVisibility(show ? View.VISIBLE : View.GONE);
1505 }
1506 }
1507
1508 /**
1509 * Sets the all apps button. This method is called from {@link Hotseat}.
1510 */
1511 public void setAllAppsButton(View allAppsButton) {
1512 mAllAppsButton = allAppsButton;
1513 }
1514
1515 public View getAllAppsButton() {
1516 return mAllAppsButton;
1517 }
1518
1519 /**
1520 * Creates a view representing a shortcut.
1521 *
1522 * @param info The data structure describing the shortcut.
1523 *
1524 * @return A View inflated from R.layout.application.
1525 */
1526 View createShortcut(ShortcutInfo info) {
1527 return createShortcut(R.layout.application,
1528 (ViewGroup) mWorkspace.getChildAt(mWorkspace.getCurrentPage()), info);
1529 }
1530
1531 /**
1532 * Creates a view representing a shortcut inflated from the specified resource.
1533 *
1534 * @param layoutResId The id of the XML layout used to create the shortcut.
1535 * @param parent The group the shortcut belongs to.
1536 * @param info The data structure describing the shortcut.
1537 *
1538 * @return A View inflated from layoutResId.
1539 */
1540 public View createShortcut(int layoutResId, ViewGroup parent, ShortcutInfo info) {
1541 BubbleTextView favorite = (BubbleTextView) mInflater.inflate(layoutResId, parent, false);
1542 favorite.applyFromShortcutInfo(info, mIconCache, true);
1543 favorite.setOnClickListener(this);
1544 favorite.setOnFocusChangeListener(mFocusHandler);
1545 return favorite;
1546 }
1547
1548 /**
1549 * Add a shortcut to the workspace.
1550 *
1551 * @param data The intent describing the shortcut.
1552 * @param cellInfo The position on screen where to create the shortcut.
1553 */
1554 private void completeAddShortcut(Intent data, long container, long screenId, int cellX, int cellY) {
1555 int[] cellXY = mTmpAddItemCellCoordinates;
1556 int[] touchXY = mPendingAddInfo.dropPos;
1557 CellLayout layout = getCellLayout(container, screenId);
1558 boolean foundCellSpan = false;
1559 ShortcutInfo info = mModel.infoFromShortcutIntent(this, data);
1560 if (info == null) {
1561 return;
1562 }
1563 final View view = createShortcut(info);
1564 // First we check if we already know the exact location where we want to add this item.
1565 if ((cellX >= 0) && (cellY >= 0)) {
1566 cellXY[0] = cellX;
1567 cellXY[1] = cellY;
1568 foundCellSpan = true;
1569 // If appropriate, either create a folder or add to an existing folder
1570 if (mWorkspace.createUserFolderIfNecessary(view, container, layout, cellXY, 0, true, null, nu🔵
1571 return;
1572 }
1573 DragObject dragObject = new DragObject();
1574 dragObject.dragInfo = info;
1575 if (mWorkspace.addToExistingFolderIfNecessary(view, layout, cellXY, 0, dragObject, true)) {
1576 return;
1577 }
1578 } else if (touchXY != null) {
1579 // when dragging and dropping, just find the closest free spot
1580 int[] result = layout.findNearestVacantArea(touchXY[0], touchXY[1], 1, 1, cellXY);
1581 foundCellSpan = result != null;
1582 } else {
1583 foundCellSpan = layout.findCellForSpan(cellXY, 1, 1);
1584 }
1585 if (!foundCellSpan) {
1586 showOutOfSpaceMessage(isHotseatLayout(layout));
1587 return;
1588 }
1589 LauncherModel.addItemToDatabase(this, info, container, screenId, cellXY[0], cellXY[1]);
1590 if (!mRestoring) {
1591 mWorkspace.addInScreen(view, container, screenId, cellXY[0], cellXY[1], 1, 1, isWorkspaceLock🔵
1592 }
1593 }
1594
1595 static int[] getSpanForWidget(Context context, ComponentName component, int minWidth,
1596 int minHeight) {
1597 Rect padding = AppWidgetHostView.getDefaultPaddingForWidget(context, component, null);
1598 // We want to account for the extra amount of padding that we are adding to the widget
1599 // to ensure that it gets the full amount of space that it has requested
1600 int requiredWidth = minWidth + padding.left + padding.right;
1601 int requiredHeight = minHeight + padding.top + padding.bottom;
1602 return CellLayout.rectToCell(requiredWidth, requiredHeight, null);
1603 }
1604
1605 static int[] getSpanForWidget(Context context, AppWidgetProviderInfo info) {
1606 return getSpanForWidget(context, info.provider, info.minWidth, info.minHeight);
1607 }
1608
1609 static int[] getMinSpanForWidget(Context context, AppWidgetProviderInfo info) {
1610 return getSpanForWidget(context, info.provider, info.minResizeWidth, info.minResizeHeight);
1611 }
1612
1613 static int[] getSpanForWidget(Context context, PendingAddWidgetInfo info) {
1614 return getSpanForWidget(context, info.componentName, info.minWidth, info.minHeight);
1615 }
1616
1617 static int[] getMinSpanForWidget(Context context, PendingAddWidgetInfo info) {
1618 return getSpanForWidget(context, info.componentName, info.minResizeWidth, info.minResizeHeight);
1619 }
1620
1621 /**
1622 * Add a widget to the workspace.
1623 *
1624 * @param appWidgetId The app widget id
1625 */
1626 @Thunk
1627 void completeAddAppWidget(int appWidgetId, long container, long screenId, AppWidgetHostView hostView,🔵
1628 ItemInfo info = mPendingAddInfo;
1629 if (appWidgetInfo == null) {
1630 appWidgetInfo = LauncherAppWidgetProviderInfo.fromProviderInfo(this, mAppWidgetManager.getApp🔵
1631 }
1632 if (appWidgetInfo.isCustomWidget) {
1633 appWidgetId = LauncherAppWidgetInfo.CUSTOM_WIDGET_ID;
1634 }
1635 LauncherAppWidgetInfo launcherInfo;
1636 launcherInfo = new LauncherAppWidgetInfo(appWidgetId, appWidgetInfo.provider);
1637 launcherInfo.spanX = info.spanX;
1638 launcherInfo.spanY = info.spanY;
1639 launcherInfo.minSpanX = info.minSpanX;
1640 launcherInfo.minSpanY = info.minSpanY;
1641 launcherInfo.user = mAppWidgetManager.getUser(appWidgetInfo);
1642 LauncherModel.addItemToDatabase(this, launcherInfo, container, screenId, info.cellX, info.cellY);
1643 if (!mRestoring) {
1644 if (hostView == null) {
1645 // Perform actual inflation because we're live
1646 launcherInfo.hostView = mAppWidgetHost.createView(this, appWidgetId, appWidgetInfo);
1647 } else {
1648 // The AppWidgetHostView has already been inflated and instantiated
1649 launcherInfo.hostView = hostView;
1650 }
1651 launcherInfo.hostView.setTag(launcherInfo);
1652 launcherInfo.hostView.setVisibility(View.VISIBLE);
1653 launcherInfo.notifyWidgetSizeChanged(this);
1654 mWorkspace.addInScreen(launcherInfo.hostView, container, screenId, info.cellX, info.cellY, la🔵
1655 addWidgetToAutoAdvanceIfNeeded(launcherInfo.hostView, appWidgetInfo);
1656 }
1657 resetAddInfo();
1658 }
1659
1660 private final BroadcastReceiver mReceiver = new BroadcastReceiver() {
1661 @Override
1662 public void onReceive(Context context, Intent intent) {
1663 final String action = intent.getAction();
1664 if (Intent.ACTION_SCREEN_OFF.equals(action)) {
1665 mUserPresent = false;
1666 mDragLayer.clearAllResizeFrames();
1667 updateAutoAdvanceState();
1668 // Reset AllApps to its initial state only if we are not in the middle of
1669 // processing a multi-step drop
1670 if (((mAppsView != null) && (mWidgetsView != null)) && (mPendingAddInfo.container == Item🔵
1671 showWorkspace(false);
1672 }
1673 } else if (Intent.ACTION_USER_PRESENT.equals(action)) {
1674 mUserPresent = true;
1675 updateAutoAdvanceState();
1676 } else if (ENABLE_DEBUG_INTENTS && DebugIntents.DELETE_DATABASE.equals(action)) {
1677 mModel.resetLoadedState(false, true);
1678 mModel.startLoader(false, PagedView.INVALID_RESTORE_PAGE, LauncherModel.LOADER_FLAG_CLEAR🔵
1679 } else if (ENABLE_DEBUG_INTENTS && DebugIntents.MIGRATE_DATABASE.equals(action)) {
1680 mModel.resetLoadedState(false, true);
1681 mModel.startLoader(false, PagedView.INVALID_RESTORE_PAGE, LauncherModel.LOADER_FLAG_CLEAR🔵
1682 } else if (LauncherAppsCompat.ACTION_MANAGED_PROFILE_ADDED.equals(action) || LauncherAppsComp🔵
1683 getModel().forceReload();
1684 }
1685 }
1686 };
1687
1688 @Override
1689 public void onAttachedToWindow() {
1690 super.onAttachedToWindow();
1691
1692 // Listen for broadcasts related to user-presence
1693 final IntentFilter filter = new IntentFilter();
1694 filter.addAction(Intent.ACTION_SCREEN_OFF);
1695 filter.addAction(Intent.ACTION_USER_PRESENT);
1696 // For handling managed profiles
1697 filter.addAction(LauncherAppsCompat.ACTION_MANAGED_PROFILE_ADDED);
1698 filter.addAction(LauncherAppsCompat.ACTION_MANAGED_PROFILE_REMOVED);
1699 if (ENABLE_DEBUG_INTENTS) {
1700 filter.addAction(DebugIntents.DELETE_DATABASE);
1701 filter.addAction(DebugIntents.MIGRATE_DATABASE);
1702 }
1703 registerReceiver(mReceiver, filter);
1704 FirstFrameAnimatorHelper.initializeDrawListener(getWindow().getDecorView());
1705 setupTransparentSystemBarsForLmp();
1706 mAttached = true;
1707 mVisible = true;
1708 }
1709
1710 /**
1711 * Sets up transparent navigation and status bars in LMP.
1712 * This method is a no-op for other platform versions.
1713 */
1714 @TargetApi(Build.VERSION_CODES.LOLLIPOP)
1715 private void setupTransparentSystemBarsForLmp() {
1716 if (Utilities.isLmpOrAbove()) {
1717 Window window = getWindow();
1718 window.getAttributes().systemUiVisibility |= (View.SYSTEM_UI_FLAG_LAYOUT_STABLE | View.SYSTEM🔵
1719 window.clearFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS | WindowManager.LayoutPa🔵
1720 window.addFlags(WindowManager.LayoutParams.FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS);
1721 window.setStatusBarColor(Color.TRANSPARENT);
1722 window.setNavigationBarColor(Color.TRANSPARENT);
1723 }
1724 }
1725
1726 @Override
1727 public void onDetachedFromWindow() {
1728 super.onDetachedFromWindow();
1729 mVisible = false;
1730 if (mAttached) {
1731 unregisterReceiver(mReceiver);
1732 mAttached = false;
1733 }
1734 updateAutoAdvanceState();
1735 }
1736
1737 public void onWindowVisibilityChanged(int visibility) {
1738 mVisible = visibility == View.VISIBLE;
1739 updateAutoAdvanceState();
1740 // The following code used to be in onResume, but it turns out onResume is called when
1741 // you're in All Apps and click home to go to the workspace. onWindowVisibilityChanged
1742 // is a more appropriate event to handle
1743 if (mVisible) {
1744 if (!mWorkspaceLoading) {
1745 final ViewTreeObserver observer = mWorkspace.getViewTreeObserver();
1746 // We want to let Launcher draw itself at least once before we force it to build
1747 // layers on all the workspace pages, so that transitioning to Launcher from other
1748 // apps is nice and speedy.
1749 observer.addOnDrawListener(new ViewTreeObserver.OnDrawListener() {
1750 private boolean mStarted = false;
1751
1752 public void onDraw() {
1753 if (mStarted) {
1754 return;
1755 }
1756 mStarted = true;
1757 // We delay the layer building a bit in order to give
1758 // other message processing a time to run. In particular
1759 // this avoids a delay in hiding the IME if it was
1760 // currently shown, because doing that may involve
1761 // some communication back with the app.
1762 mWorkspace.postDelayed(mBuildLayersRunnable, 500);
1763 final ViewTreeObserver.OnDrawListener listener = this;
1764 mWorkspace.post(new Runnable() {
1765 public void run() {
1766 if ((mWorkspace != null) && (mWorkspace.getViewTreeObserver() != null)) {
1767 mWorkspace.getViewTreeObserver().removeOnDrawListener(listener);
1768 }
1769 }
1770 });
1771 return;
1772 }
1773 });
1774 }
1775 clearTypedText();
1776 }
1777 }
1778
1779 @Thunk
1780 void sendAdvanceMessage(long delay) {
1781 mHandler.removeMessages(ADVANCE_MSG);
1782 Message msg = mHandler.obtainMessage(ADVANCE_MSG);
1783 mHandler.sendMessageDelayed(msg, delay);
1784 mAutoAdvanceSentTime = System.currentTimeMillis();
1785 }
1786
1787 @Thunk
1788 void updateAutoAdvanceState() {
1789 boolean autoAdvanceRunning = (mVisible && mUserPresent) && (!mWidgetsToAdvance.isEmpty());
1790 if (autoAdvanceRunning != mAutoAdvanceRunning) {
1791 mAutoAdvanceRunning = autoAdvanceRunning;
1792 if (autoAdvanceRunning) {
1793 long delay = (mAutoAdvanceTimeLeft == (-1)) ? mAdvanceInterval : mAutoAdvanceTimeLeft;
1794 sendAdvanceMessage(delay);
1795 } else {
1796 if (!mWidgetsToAdvance.isEmpty()) {
1797 mAutoAdvanceTimeLeft = Math.max(0, mAdvanceInterval - (System.currentTimeMillis() - m🔵
1798 }
1799 mHandler.removeMessages(ADVANCE_MSG);
1800 mHandler.removeMessages(0);// Remove messages sent using postDelayed()
1801
1802 }
1803 }
1804 }
1805
1806 private final Handler mHandler = new Handler() {
1807 @Override
1808 public void handleMessage(Message msg) {
1809 if (msg.what == ADVANCE_MSG) {
1810 int i = 0;
1811 for (View key: mWidgetsToAdvance.keySet()) {
1812 final View v = key.findViewById(mWidgetsToAdvance.get(key).autoAdvanceViewId);
1813 final int delay = mAdvanceStagger * i;
1814 if (v instanceof Advanceable) {
1815 postDelayed(new Runnable() {
1816 public void run() {
1817 ((Advanceable) v).advance();
1818 }
1819 }, delay);
1820 }
1821 i++;
1822 }
1823 sendAdvanceMessage(mAdvanceInterval);
1824 }
1825 }
1826 };
1827
1828 void addWidgetToAutoAdvanceIfNeeded(View hostView, AppWidgetProviderInfo appWidgetInfo) {
1829 if ((appWidgetInfo == null) || (appWidgetInfo.autoAdvanceViewId == (-1))) {
1830 return;
1831 }
1832 View v = hostView.findViewById(appWidgetInfo.autoAdvanceViewId);
1833 if (v instanceof Advanceable) {
1834 mWidgetsToAdvance.put(hostView, appWidgetInfo);
1835 ((Advanceable) (v)).fyiWillBeAdvancedByHostKThx();
1836 updateAutoAdvanceState();
1837 }
1838 }
1839
1840 void removeWidgetToAutoAdvance(View hostView) {
1841 if (mWidgetsToAdvance.containsKey(hostView)) {
1842 mWidgetsToAdvance.remove(hostView);
1843 updateAutoAdvanceState();
1844 }
1845 }
1846
1847 public void removeAppWidget(LauncherAppWidgetInfo launcherInfo) {
1848 removeWidgetToAutoAdvance(launcherInfo.hostView);
1849 launcherInfo.hostView = null;
1850 }
1851
1852 public void showOutOfSpaceMessage(boolean isHotseatLayout) {
1853 int strId = (isHotseatLayout) ? R.string.hotseat_out_of_space : R.string.out_of_space;
1854 Toast.makeText(this, getString(strId), Toast.LENGTH_SHORT).show();
1855 }
1856
1857 public DragLayer getDragLayer() {
1858 return mDragLayer;
1859 }
1860
1861 public AppsContainerView getAppsView() {
1862 return mAppsView;
1863 }
1864
1865 public WidgetsContainerView getWidgetsView() {
1866 return mWidgetsView;
1867 }
1868
1869 public Workspace getWorkspace() {
1870 return mWorkspace;
1871 }
1872
1873 public Hotseat getHotseat() {
1874 return mHotseat;
1875 }
1876
1877 public ViewGroup getOverviewPanel() {
1878 return mOverviewPanel;
1879 }
1880
1881 public SearchDropTargetBar getSearchBar() {
1882 return mSearchDropTargetBar;
1883 }
1884
1885 public LauncherAppWidgetHost getAppWidgetHost() {
1886 return mAppWidgetHost;
1887 }
1888
1889 public LauncherModel getModel() {
1890 return mModel;
1891 }
1892
1893 protected SharedPreferences getSharedPrefs() {
1894 return mSharedPrefs;
1895 }
1896
1897 public void closeSystemDialogs() {
1898 getWindow().closeAllPanels();
1899
1900 // Whatever we were doing is hereby canceled.
1901 setWaitingForResult(false);
1902 }
1903
1904 @Override
1905 protected void onNewIntent(Intent intent) {
1906 long startTime = 0;
1907 if (DEBUG_RESUME_TIME) {
1908 startTime = System.currentTimeMillis();
1909 }
1910 super.onNewIntent(intent);
1911 // Close the menu
1912 if (Intent.ACTION_MAIN.equals(intent.getAction())) {
1913 // also will cancel mWaitingForResult.
1914 closeSystemDialogs();
1915 final boolean alreadyOnHome = mHasFocus && ((intent.getFlags() & Intent.FLAG_ACTIVITY_BROUGHT🔵
1916 if (mWorkspace == null) {
1917 // Can be cases where mWorkspace is null, this prevents a NPE
1918 return;
1919 }
1920 Folder openFolder = mWorkspace.getOpenFolder();
1921 // In all these cases, only animate if we're already on home
1922 mWorkspace.exitWidgetResizeMode();
1923 boolean moveToDefaultScreen = (mLauncherCallbacks != null) ? mLauncherCallbacks.shouldMoveToD🔵
1924 if ((((alreadyOnHome && (mState == State.WORKSPACE)) && (!mWorkspace.isTouchActive())) && (op🔵
1925 mWorkspace.moveToDefaultScreen(true);
1926 }
1927 closeFolder();
1928 exitSpringLoadedDragMode();
1929 // If we are already on home, then just animate back to the workspace,
1930 // otherwise, just wait until onResume to set the state back to Workspace
1931 if (alreadyOnHome) {
1932 showWorkspace(true);
1933 } else {
1934 mOnResumeState = State.WORKSPACE;
1935 }
1936 final View v = getWindow().peekDecorView();
1937 if ((v != null) && (v.getWindowToken() != null)) {
1938 InputMethodManager imm = ((InputMethodManager) (getSystemService(INPUT_METHOD_SERVICE)));
1939 imm.hideSoftInputFromWindow(v.getWindowToken(), 0);
1940 }
1941 // Reset the apps view
1942 if ((!alreadyOnHome) && (mAppsView != null)) {
1943 mAppsView.scrollToTop();
1944 }
1945 // Reset the widgets view
1946 if ((!alreadyOnHome) && (mWidgetsView != null)) {
1947 mWidgetsView.scrollToTop();
1948 }
1949 if (mLauncherCallbacks != null) {
1950 mLauncherCallbacks.onHomeIntent();
1951 }
1952 }
1953 if (DEBUG_RESUME_TIME) {
1954 Log.d(TAG, "Time spent in onNewIntent: " + (System.currentTimeMillis() - startTime));
1955 }
1956 if (mLauncherCallbacks != null) {
1957 mLauncherCallbacks.onNewIntent(intent);
1958 }
1959 }
1960
1961 @Override
1962 public void onRestoreInstanceState(Bundle state) {
1963 super.onRestoreInstanceState(state);
1964 for (int page: mSynchronouslyBoundPages) {
1965 mWorkspace.restoreInstanceStateForChild(page);
1966 }
1967 }
1968
1969 @Override
1970 protected void onSaveInstanceState(Bundle outState) {
1971 if (mWorkspace.getChildCount() > 0) {
1972 outState.putInt(RUNTIME_STATE_CURRENT_SCREEN, mWorkspace.getCurrentPageOffsetFromCustomConten🔵
1973 }
1974 super.onSaveInstanceState(outState);
1975 outState.putInt(RUNTIME_STATE, mState.ordinal());
1976 // We close any open folder since it will not be re-opened, and we need to make sure
1977 // this state is reflected.
1978 closeFolder();
1979 if (((mPendingAddInfo.container != ItemInfo.NO_ID) && (mPendingAddInfo.screenId > (-1))) && mWait🔵
1980 outState.putLong(RUNTIME_STATE_PENDING_ADD_CONTAINER, mPendingAddInfo.container);
1981 outState.putLong(RUNTIME_STATE_PENDING_ADD_SCREEN, mPendingAddInfo.screenId);
1982 outState.putInt(RUNTIME_STATE_PENDING_ADD_CELL_X, mPendingAddInfo.cellX);
1983 outState.putInt(RUNTIME_STATE_PENDING_ADD_CELL_Y, mPendingAddInfo.cellY);
1984 outState.putInt(RUNTIME_STATE_PENDING_ADD_SPAN_X, mPendingAddInfo.spanX);
1985 outState.putInt(RUNTIME_STATE_PENDING_ADD_SPAN_Y, mPendingAddInfo.spanY);
1986 outState.putParcelable(RUNTIME_STATE_PENDING_ADD_WIDGET_INFO, mPendingAddWidgetInfo);
1987 outState.putInt(RUNTIME_STATE_PENDING_ADD_WIDGET_ID, mPendingAddWidgetId);
1988 }
1989 if ((mFolderInfo != null) && mWaitingForResult) {
1990 outState.putBoolean(RUNTIME_STATE_PENDING_FOLDER_RENAME, true);
1991 outState.putLong(RUNTIME_STATE_PENDING_FOLDER_RENAME_ID, mFolderInfo.id);
1992 }
1993 // Save the current widgets tray?
1994 // TODO(hyunyoungs)
1995 outState.putSerializable(RUNTIME_STATE_VIEW_IDS, mItemIdToViewId);
1996 if (mLauncherCallbacks != null) {
1997 mLauncherCallbacks.onSaveInstanceState(outState);
1998 }
1999 }
2000
2001 @Override
2002 public void onDestroy() {
2003 super.onDestroy();
2004
2005 // Remove all pending runnables
2006 mHandler.removeMessages(ADVANCE_MSG);
2007 mHandler.removeMessages(0);
2008 mWorkspace.removeCallbacks(mBuildLayersRunnable);
2009
2010 // Stop callbacks from LauncherModel
2011 LauncherAppState app = (LauncherAppState.getInstance());
2012
2013 // It's possible to receive onDestroy after a new Launcher activity has
2014 // been created. In this case, don't interfere with the new Launcher.
2015 if (mModel.isCurrentCallbacks(this)) {
2016 mModel.stopLoader();
2017 app.setLauncher(null);
2018 }
2019
2020 try {
2021 mAppWidgetHost.stopListening();
2022 } catch (NullPointerException ex) {
2023 Log.w(TAG, "problem while stopping AppWidgetHost during Launcher destruction", ex);
2024 }
2025 mAppWidgetHost = null;
2026
2027 mWidgetsToAdvance.clear();
2028
2029 TextKeyListener.getInstance().release();
2030
2031 // Disconnect any of the callbacks and drawables associated with ItemInfos on the workspace
2032 // to prevent leaking Launcher activities on orientation change.
2033 if (mModel != null) {
2034 mModel.unbindItemInfosAndClearQueuedBindRunnables();
2035 }
2036
2037 getContentResolver().unregisterContentObserver(mWidgetObserver);
2038 unregisterReceiver(mCloseSystemDialogsReceiver);
2039
2040 mDragLayer.clearAllResizeFrames();
2041 ((ViewGroup) mWorkspace.getParent()).removeAllViews();
2042 mWorkspace.removeAllWorkspaceScreens();
2043 mWorkspace = null;
2044 mDragController = null;
2045
2046 LauncherAnimUtils.onDestroyActivity();
2047
2048 if (mLauncherCallbacks != null) {
2049 mLauncherCallbacks.onDestroy();
2050 }
2051 }
2052
2053 public DragController getDragController() {
2054 return mDragController;
2055 }
2056
2057 @Override
2058 public void startActivityForResult(Intent intent, int requestCode) {
2059 onStartForResult(requestCode);
2060 super.startActivityForResult(intent, requestCode);
2061 }
2062
2063 @Override
2064 public void startIntentSenderForResult (IntentSender intent, int requestCode,
2065 Intent fillInIntent, int flagsMask, int flagsValues, int extraFlags, Bundle options) {
2066 onStartForResult(requestCode);
2067 try {
2068 super.startIntentSenderForResult(intent, requestCode,
2069 fillInIntent, flagsMask, flagsValues, extraFlags, options);
2070 } catch (IntentSender.SendIntentException e) {
2071 throw new ActivityNotFoundException();
2072 }
2073 }
2074
2075 private void onStartForResult(int requestCode) {
2076 if (requestCode >= 0) {
2077 setWaitingForResult(true);
2078 }
2079 }
2080
2081 /**
2082 * Indicates that we want global search for this activity by setting the globalSearch
2083 * argument for {@link #startSearch} to true.
2084 */
2085 @Override
2086 public void startSearch(String initialQuery, boolean selectInitialQuery,
2087 Bundle appSearchData, boolean globalSearch) {
2088
2089 showWorkspace(true);
2090
2091 if (initialQuery == null) {
2092 // Use any text typed in the launcher as the initial query
2093 initialQuery = getTypedText();
2094 }
2095 if (appSearchData == null) {
2096 appSearchData = new Bundle();
2097 appSearchData.putString("source", "launcher-search");
2098 }
2099 Rect sourceBounds = new Rect();
2100 if (mSearchDropTargetBar != null) {
2101 sourceBounds = mSearchDropTargetBar.getSearchBarBounds();
2102 }
2103
2104 boolean clearTextImmediately = startSearch(initialQuery, selectInitialQuery,
2105 appSearchData, sourceBounds);
2106 if (clearTextImmediately) {
2107 clearTypedText();
2108 }
2109 }
2110
2111 /**
2112 * Start a text search.
2113 *
2114 * @return {@code true} if the search will start immediately, so any further keypresses
2115 * will be handled directly by the search UI. {@code false} if {@link Launcher} should continue
2116 * to buffer keypresses.
2117 */
2118 public boolean startSearch(String initialQuery,
2119 boolean selectInitialQuery, Bundle appSearchData, Rect sourceBounds) {
2120 if (mLauncherCallbacks != null && mLauncherCallbacks.providesSearch()) {
2121 return mLauncherCallbacks.startSearch(initialQuery, selectInitialQuery, appSearchData,
2122 sourceBounds);
2123 }
2124
2125 startGlobalSearch(initialQuery, selectInitialQuery,
2126 appSearchData, sourceBounds);
2127 return false;
2128 }
2129
2130 /**
2131 * Starts the global search activity. This code is a copied from SearchManager
2132 */
2133 private void startGlobalSearch(String initialQuery,
2134 boolean selectInitialQuery, Bundle appSearchData, Rect sourceBounds) {
2135 final SearchManager searchManager =
2136 (SearchManager) getSystemService(Context.SEARCH_SERVICE);
2137 ComponentName globalSearchActivity = searchManager.getGlobalSearchActivity();
2138 if (globalSearchActivity == null) {
2139 Log.w(TAG, "No global search activity found.");
2140 return;
2141 }
2142 Intent intent = new Intent(SearchManager.INTENT_ACTION_GLOBAL_SEARCH);
2143 intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
2144 intent.setComponent(globalSearchActivity);
2145 // Make sure that we have a Bundle to put source in
2146 if (appSearchData == null) {
2147 appSearchData = new Bundle();
2148 } else {
2149 appSearchData = new Bundle(appSearchData);
2150 }
2151 // Set source to package name of app that starts global search if not set already.
2152 if (!appSearchData.containsKey("source")) {
2153 appSearchData.putString("source", getPackageName());
2154 }
2155 intent.putExtra(SearchManager.APP_DATA, appSearchData);
2156 if (!TextUtils.isEmpty(initialQuery)) {
2157 intent.putExtra(SearchManager.QUERY, initialQuery);
2158 }
2159 if (selectInitialQuery) {
2160 intent.putExtra(SearchManager.EXTRA_SELECT_QUERY, selectInitialQuery);
2161 }
2162 intent.setSourceBounds(sourceBounds);
2163 try {
2164 startActivity(intent);
2165 } catch (ActivityNotFoundException ex) {
2166 Log.e(TAG, "Global search activity not found: " + globalSearchActivity);
2167 }
2168 }
2169
2170 public boolean isOnCustomContent() {
2171 return mWorkspace.isOnOrMovingToCustomContent();
2172 }
2173
2174 @Override
2175 public boolean onPrepareOptionsMenu(Menu menu) {
2176 super.onPrepareOptionsMenu(menu);
2177 if (!isOnCustomContent()) {
2178 // Close any open folders
2179 closeFolder();
2180 // Stop resizing any widgets
2181 mWorkspace.exitWidgetResizeMode();
2182 if (!mWorkspace.isInOverviewMode()) {
2183 // Show the overview mode
2184 showOverviewMode(true);
2185 } else {
2186 showWorkspace(true);
2187 }
2188 }
2189 if (mLauncherCallbacks != null) {
2190 return mLauncherCallbacks.onPrepareOptionsMenu(menu);
2191 }
2192
2193 return false;
2194 }
2195
2196 @Override
2197 public boolean onSearchRequested() {
2198 startSearch(null, false, null, true);
2199 // Use a custom animation for launching search
2200 return true;
2201 }
2202
2203 public boolean isWorkspaceLocked() {
2204 return mWorkspaceLoading || mWaitingForResult;
2205 }
2206
2207 public boolean isWorkspaceLoading() {
2208 return mWorkspaceLoading;
2209 }
2210
2211 private void setWorkspaceLoading(boolean value) {
2212 boolean isLocked = isWorkspaceLocked();
2213 mWorkspaceLoading = value;
2214 if (isLocked != isWorkspaceLocked()) {
2215 onWorkspaceLockedChanged();
2216 }
2217 }
2218
2219 private void setWaitingForResult(boolean value) {
2220 boolean isLocked = isWorkspaceLocked();
2221 mWaitingForResult = value;
2222 if (isLocked != isWorkspaceLocked()) {
2223 onWorkspaceLockedChanged();
2224 }
2225 }
2226
2227 protected void onWorkspaceLockedChanged() {
2228 if (mLauncherCallbacks != null) {
2229 mLauncherCallbacks.onWorkspaceLockedChanged();
2230 }
2231 }
2232
2233 private void resetAddInfo() {
2234 mPendingAddInfo.container = ItemInfo.NO_ID;
2235 mPendingAddInfo.screenId = -1;
2236 mPendingAddInfo.cellX = mPendingAddInfo.cellY = -1;
2237 mPendingAddInfo.spanX = mPendingAddInfo.spanY = -1;
2238 mPendingAddInfo.minSpanX = mPendingAddInfo.minSpanY = -1;
2239 mPendingAddInfo.dropPos = null;
2240 }
2241
2242 void addAppWidgetImpl(final int appWidgetId, final ItemInfo info, final
2243 AppWidgetHostView boundWidget, final LauncherAppWidgetProviderInfo appWidgetInfo) {
2244 addAppWidgetImpl(appWidgetId, info, boundWidget, appWidgetInfo, 0);
2245 }
2246
2247 void addAppWidgetImpl(final int appWidgetId, final ItemInfo info,
2248 final AppWidgetHostView boundWidget, final LauncherAppWidgetProviderInfo appWidgetInfo,
2249 int delay) {
2250 if (appWidgetInfo.configure != null) {
2251 mPendingAddWidgetInfo = appWidgetInfo;
2252 mPendingAddWidgetId = appWidgetId;
2253
2254 // Launch over to configure widget, if needed
2255 mAppWidgetManager.startConfigActivity(appWidgetInfo, appWidgetId, this,
2256 mAppWidgetHost, REQUEST_CREATE_APPWIDGET);
2257
2258 } else {
2259 // Otherwise just add it
2260 Runnable onComplete = new Runnable() {
2261 @Override
2262 public void run() {
2263 // Exit spring loaded mode if necessary after adding the widget
2264 exitSpringLoadedDragModeDelayed(true, EXIT_SPRINGLOADED_MODE_SHORT_TIMEOUT,
2265 null);
2266 }
2267 };
2268 completeAddAppWidget(appWidgetId, info.container, info.screenId, boundWidget,
2269 appWidgetInfo);
2270 mWorkspace.removeExtraEmptyScreenDelayed(true, onComplete, delay, false);
2271 }
2272 }
2273
2274 protected void moveToCustomContentScreen(boolean animate) {
2275 // Close any folders that may be open.
2276 closeFolder();
2277 mWorkspace.moveToCustomContentScreen(animate);
2278 }
2279
2280 public void addPendingItem(PendingAddItemInfo info, long container, long screenId, int[] cell, int sp🔵
2281 switch (info.itemType) {
2282 case LauncherSettings.Favorites.ITEM_TYPE_CUSTOM_APPWIDGET :
2283 case LauncherSettings.Favorites.ITEM_TYPE_APPWIDGET :
2284 int[] span = new int[2];
2285 span[0] = spanX;
2286 span[1] = spanY;
2287 addAppWidgetFromDrop(((PendingAddWidgetInfo) (info)), container, screenId, cell, span);
2288 break;
2289 case LauncherSettings.Favorites.ITEM_TYPE_SHORTCUT :
2290 processShortcutFromDrop(info.componentName, container, screenId, cell);
2291 break;
2292 default :
2293 throw new IllegalStateException("Unknown item type: " + info.itemType);
2294 }
2295 }
2296
2297 /**
2298 * Process a shortcut drop.
2299 *
2300 * @param componentName The name of the component
2301 * @param screenId The ID of the screen where it should be added
2302 * @param cell The cell it should be added to, optional
2303 */
2304 private void processShortcutFromDrop(ComponentName componentName, long container, long screenId,
2305 int[] cell) {
2306 resetAddInfo();
2307 mPendingAddInfo.container = container;
2308 mPendingAddInfo.screenId = screenId;
2309 mPendingAddInfo.dropPos = null;
2310
2311 if (cell != null) {
2312 mPendingAddInfo.cellX = cell[0];
2313 mPendingAddInfo.cellY = cell[1];
2314 }
2315
2316 Intent createShortcutIntent = new Intent(Intent.ACTION_CREATE_SHORTCUT);
2317 createShortcutIntent.setComponent(componentName);
2318 processShortcut(createShortcutIntent);
2319 }
2320
2321 /**
2322 * Process a widget drop.
2323 *
2324 * @param info The PendingAppWidgetInfo of the widget being added.
2325 * @param screenId The ID of the screen where it should be added
2326 * @param cell The cell it should be added to, optional
2327 */
2328 private void addAppWidgetFromDrop(PendingAddWidgetInfo info, long container, long screenId, int[] cel🔵
2329 resetAddInfo();
2330 mPendingAddInfo.container = info.container = container;
2331 mPendingAddInfo.screenId = info.screenId = screenId;
2332 mPendingAddInfo.dropPos = null;
2333 mPendingAddInfo.minSpanX = info.minSpanX;
2334 mPendingAddInfo.minSpanY = info.minSpanY;
2335 if (cell != null) {
2336 mPendingAddInfo.cellX = cell[0];
2337 mPendingAddInfo.cellY = cell[1];
2338 }
2339 if (span != null) {
2340 mPendingAddInfo.spanX = span[0];
2341 mPendingAddInfo.spanY = span[1];
2342 }
2343 AppWidgetHostView hostView = info.boundWidget;
2344 int appWidgetId;
2345 if (hostView != null) {
2346 appWidgetId = hostView.getAppWidgetId();
2347 addAppWidgetImpl(appWidgetId, info, hostView, info.info);
2348 } else {
2349 // In this case, we either need to start an activity to get permission to bind
2350 // the widget, or we need to start an activity to configure the widget, or both.
2351 appWidgetId = getAppWidgetHost().allocateAppWidgetId();
2352 Bundle options = info.bindOptions;
2353 boolean success = mAppWidgetManager.bindAppWidgetIdIfAllowed(appWidgetId, info.info, options)🔵
2354 if (success) {
2355 addAppWidgetImpl(appWidgetId, info, null, info.info);
2356 } else {
2357 mPendingAddWidgetInfo = info.info;
2358 Intent intent = new Intent(AppWidgetManager.ACTION_APPWIDGET_BIND);
2359 intent.putExtra(AppWidgetManager.EXTRA_APPWIDGET_ID, appWidgetId);
2360 intent.putExtra(AppWidgetManager.EXTRA_APPWIDGET_PROVIDER, info.componentName);
2361 mAppWidgetManager.getUser(mPendingAddWidgetInfo).addToIntent(intent, AppWidgetManager.EXT🔵
2362 // TODO: we need to make sure that this accounts for the options bundle.
2363 // intent.putExtra(AppWidgetManager.EXTRA_APPWIDGET_OPTIONS, options);
2364 startActivityForResult(intent, REQUEST_BIND_APPWIDGET);
2365 }
2366 }
2367 }
2368
2369 void processShortcut(Intent intent) {
2370 Utilities.startActivityForResultSafely(this, intent, REQUEST_CREATE_SHORTCUT);
2371 }
2372
2373 void processWallpaper(Intent intent) {
2374 startActivityForResult(intent, REQUEST_PICK_WALLPAPER);
2375 }
2376
2377 FolderIcon addFolder(CellLayout layout, long container, final long screenId, int cellX, int cellY) {
2378 final FolderInfo folderInfo = new FolderInfo();
2379 folderInfo.title = getText(R.string.folder_name);
2380 // Update the model
2381 LauncherModel.addItemToDatabase(this, folderInfo, container, screenId, cellX, cellY);
2382 sFolders.put(folderInfo.id, folderInfo);
2383 // Create the view
2384 FolderIcon newFolder = FolderIcon.fromXml(R.layout.folder_icon, this, layout, folderInfo, mIconCa🔵
2385 mWorkspace.addInScreen(newFolder, container, screenId, cellX, cellY, 1, 1, isWorkspaceLocked());
2386 // Force measure the new folder icon
2387 CellLayout parent = mWorkspace.getParentCellLayoutForView(newFolder);
2388 parent.getShortcutsAndWidgets().measureChild(newFolder);
2389 return newFolder;
2390 }
2391
2392 void removeFolder(FolderInfo folder) {
2393 sFolders.remove(folder.id);
2394 }
2395
2396 /**
2397 * Registers various content observers. The current implementation registers
2398 * only a favorites observer to keep track of the favorites applications.
2399 */
2400 private void registerContentObservers() {
2401 ContentResolver resolver = getContentResolver();
2402 resolver.registerContentObserver(LauncherProvider.CONTENT_APPWIDGET_RESET_URI,
2403 true, mWidgetObserver);
2404 }
2405
2406 @Override
2407 public boolean dispatchKeyEvent(KeyEvent event) {
2408 if (event.getAction() == KeyEvent.ACTION_DOWN) {
2409 switch (event.getKeyCode()) {
2410 case KeyEvent.KEYCODE_HOME:
2411 return true;
2412 case KeyEvent.KEYCODE_VOLUME_DOWN:
2413 if (Utilities.isPropertyEnabled(DUMP_STATE_PROPERTY)) {
2414 dumpState();
2415 return true;
2416 }
2417 break;
2418 }
2419 } else if (event.getAction() == KeyEvent.ACTION_UP) {
2420 switch (event.getKeyCode()) {
2421 case KeyEvent.KEYCODE_HOME:
2422 return true;
2423 }
2424 }
2425
2426 return super.dispatchKeyEvent(event);
2427 }
2428
2429 @Override
2430 public void onBackPressed() {
2431 if ((mLauncherCallbacks != null) && mLauncherCallbacks.handleBackPressed()) {
2432 return;
2433 }
2434 LauncherAccessibilityDelegate delegate = LauncherAppState.getInstance().getAccessibilityDelegate(🔵
2435 if ((delegate != null) && delegate.onBackPressed()) {
2436 return;
2437 }
2438 if (isAppsViewVisible()) {
2439 showWorkspace(true);
2440 } else if (isWidgetsViewVisible()) {
2441 showOverviewMode(true);
2442 } else if (mWorkspace.isInOverviewMode()) {
2443 mWorkspace.exitOverviewMode(true);
2444 } else if (mWorkspace.getOpenFolder() != null) {
2445 Folder openFolder = mWorkspace.getOpenFolder();
2446 if (openFolder.isEditingName()) {
2447 openFolder.dismissEditingName();
2448 } else {
2449 closeFolder();
2450 }
2451 } else {
2452 mWorkspace.exitWidgetResizeMode();
2453 // Back button is a no-op here, but give at least some feedback for the button press
2454 mWorkspace.showOutlinesTemporarily();
2455 }
2456 }
2457
2458 /**
2459 * Re-listen when widgets are reset.
2460 */
2461 @Thunk
2462 void onAppWidgetReset() {
2463 if (mAppWidgetHost != null) {
2464 mAppWidgetHost.startListening();
2465 }
2466 }
2467
2468 /**
2469 * Launches the intent referred by the clicked shortcut.
2470 *
2471 * @param v The view representing the clicked shortcut.
2472 */
2473 public void onClick(View v) {
2474 // Make sure that rogue clicks don't get through while allapps is launching, or after the
2475 // view has detached (it's possible for this to happen if the view is removed mid touch).
2476 if (v.getWindowToken() == null) {
2477 return;
2478 }
2479
2480 if (!mWorkspace.isFinishedSwitchingState()) {
2481 return;
2482 }
2483
2484 if (v instanceof Workspace) {
2485 if (mWorkspace.isInOverviewMode()) {
2486 mWorkspace.exitOverviewMode(true);
2487 }
2488 return;
2489 }
2490
2491 if (v instanceof CellLayout) {
2492 if (mWorkspace.isInOverviewMode()) {
2493 mWorkspace.exitOverviewMode(mWorkspace.indexOfChild(v), true);
2494 }
2495 }
2496
2497 Object tag = v.getTag();
2498 if (tag instanceof ShortcutInfo) {
2499 onClickAppShortcut(v);
2500 } else if (tag instanceof FolderInfo) {
2501 if (v instanceof FolderIcon) {
2502 onClickFolderIcon(v);
2503 }
2504 } else if (v == mAllAppsButton) {
2505 onClickAllAppsButton(v);
2506 } else if (tag instanceof AppInfo) {
2507 startAppShortcutOrInfoActivity(v);
2508 } else if (tag instanceof LauncherAppWidgetInfo) {
2509 if (v instanceof PendingAppWidgetHostView) {
2510 onClickPendingWidget((PendingAppWidgetHostView) v);
2511 }
2512 }
2513 }
2514
2515 public void onClickPagedViewIcon(View v) {
2516 startAppShortcutOrInfoActivity(v);
2517 if (mLauncherCallbacks != null) {
2518 mLauncherCallbacks.onClickPagedViewIcon(v);
2519 }
2520 }
2521
2522 public boolean onTouch(View v, MotionEvent event) {
2523 return false;
2524 }
2525
2526 /**
2527 * Event handler for the app widget view which has not fully restored.
2528 */
2529 public void onClickPendingWidget(final PendingAppWidgetHostView v) {
2530 if (mIsSafeModeEnabled) {
2531 Toast.makeText(this, R.string.safemode_widget_error, Toast.LENGTH_SHORT).show();
2532 return;
2533 }
2534
2535 final LauncherAppWidgetInfo info = (LauncherAppWidgetInfo) v.getTag();
2536 if (v.isReadyForClickSetup()) {
2537 int widgetId = info.appWidgetId;
2538 AppWidgetProviderInfo appWidgetInfo = mAppWidgetManager.getAppWidgetInfo(widgetId);
2539 if (appWidgetInfo != null) {
2540 mPendingAddWidgetInfo = LauncherAppWidgetProviderInfo.fromProviderInfo(
2541 this, appWidgetInfo);
2542 mPendingAddInfo.copyFrom(info);
2543 mPendingAddWidgetId = widgetId;
2544
2545 AppWidgetManagerCompat.getInstance(this).startConfigActivity(appWidgetInfo,
2546 info.appWidgetId, this, mAppWidgetHost, REQUEST_RECONFIGURE_APPWIDGET);
2547 }
2548 } else if (info.installProgress < 0) {
2549 // The install has not been queued
2550 final String packageName = info.providerName.getPackageName();
2551 showBrokenAppInstallDialog(packageName,
2552 new DialogInterface.OnClickListener() {
2553 public void onClick(DialogInterface dialog, int id) {
2554 startActivitySafely(v, LauncherModel.getMarketIntent(packageName), info);
2555 }
2556 });
2557 } else {
2558 // Download has started.
2559 final String packageName = info.providerName.getPackageName();
2560 startActivitySafely(v, LauncherModel.getMarketIntent(packageName), info);
2561 }
2562 }
2563
2564 /**
2565 * Event handler for the "grid" button that appears on the home screen, which
2566 * enters all apps mode.
2567 *
2568 * @param v The view that was clicked.
2569 */
2570 protected void onClickAllAppsButton(View v) {
2571 if (LOGD) {
2572 Log.d(TAG, "onClickAllAppsButton");
2573 }
2574 if (isAppsViewVisible()) {
2575 showWorkspace(true);
2576 } else {
2577 /* animated */
2578 /* resetListToTop */
2579 showAppsView(true, false);
2580 }
2581 }
2582
2583 private void showBrokenAppInstallDialog(final String packageName,
2584 DialogInterface.OnClickListener onSearchClickListener) {
2585 new AlertDialog.Builder(this)
2586 .setTitle(R.string.abandoned_promises_title)
2587 .setMessage(R.string.abandoned_promise_explanation)
2588 .setPositiveButton(R.string.abandoned_search, onSearchClickListener)
2589 .setNeutralButton(R.string.abandoned_clean_this,
2590 new DialogInterface.OnClickListener() {
2591 public void onClick(DialogInterface dialog, int id) {
2592 final UserHandleCompat user = UserHandleCompat.myUserHandle();
2593 mWorkspace.removeAbandonedPromise(packageName, user);
2594 }
2595 })
2596 .create().show();
2597 return;
2598 }
2599
2600 /**
2601 * Event handler for an app shortcut click.
2602 *
2603 * @param v The view that was clicked. Must be a tagged with a {@link ShortcutInfo}.
2604 */
2605 protected void onClickAppShortcut(final View v) {
2606 if (LOGD) Log.d(TAG, "onClickAppShortcut");
2607 Object tag = v.getTag();
2608 if (!(tag instanceof ShortcutInfo)) {
2609 throw new IllegalArgumentException("Input must be a Shortcut");
2610 }
2611
2612 // Open shortcut
2613 final ShortcutInfo shortcut = (ShortcutInfo) tag;
2614
2615 if (shortcut.isDisabled != 0) {
2616 int error = R.string.activity_not_available;
2617 if ((shortcut.isDisabled & ShortcutInfo.FLAG_DISABLED_SAFEMODE) != 0) {
2618 error = R.string.safemode_shortcut_error;
2619 }
2620 Toast.makeText(this, error, Toast.LENGTH_SHORT).show();
2621 return;
2622 }
2623
2624 final Intent intent = shortcut.intent;
2625
2626 // Check for special shortcuts
2627 if (intent.getComponent() != null) {
2628 final String shortcutClass = intent.getComponent().getClassName();
2629
2630 if (shortcutClass.equals(MemoryDumpActivity.class.getName())) {
2631 MemoryDumpActivity.startDump(this);
2632 return;
2633 } else if (shortcutClass.equals(ToggleWeightWatcher.class.getName())) {
2634 toggleShowWeightWatcher();
2635 return;
2636 }
2637 }
2638
2639 // Check for abandoned promise
2640 if ((v instanceof BubbleTextView)
2641 && shortcut.isPromise()
2642 && !shortcut.hasStatusFlag(ShortcutInfo.FLAG_INSTALL_SESSION_ACTIVE)) {
2643 showBrokenAppInstallDialog(
2644 shortcut.getTargetComponent().getPackageName(),
2645 new DialogInterface.OnClickListener() {
2646 public void onClick(DialogInterface dialog, int id) {
2647 startAppShortcutOrInfoActivity(v);
2648 }
2649 });
2650 return;
2651 }
2652
2653 // Start activities
2654 startAppShortcutOrInfoActivity(v);
2655
2656 if (mLauncherCallbacks != null) {
2657 mLauncherCallbacks.onClickAppShortcut(v);
2658 }
2659 }
2660
2661 @Thunk
2662 void startAppShortcutOrInfoActivity(View v) {
2663 Object tag = v.getTag();
2664 final ShortcutInfo shortcut;
2665 final Intent intent;
2666 if (tag instanceof ShortcutInfo) {
2667 shortcut = ((ShortcutInfo) (tag));
2668 intent = shortcut.intent;
2669 int[] pos = new int[2];
2670 v.getLocationOnScreen(pos);
2671 intent.setSourceBounds(new Rect(pos[0], pos[1], pos[0] + v.getWidth(), pos[1] + v.getHeight()🔵
2672 } else if (tag instanceof AppInfo) {
2673 shortcut = null;
2674 intent = ((AppInfo) (tag)).intent;
2675 } else {
2676 throw new IllegalArgumentException("Input must be a Shortcut or AppInfo");
2677 }
2678 boolean success = startActivitySafely(v, intent, tag);
2679 mStats.recordLaunch(intent, shortcut);
2680 if (success && (v instanceof BubbleTextView)) {
2681 mWaitingForResume = ((BubbleTextView) (v));
2682 mWaitingForResume.setStayPressed(true);
2683 }
2684 }
2685
2686 /**
2687 * Event handler for a folder icon click.
2688 *
2689 * @param v The view that was clicked. Must be an instance of {@link FolderIcon}.
2690 */
2691 protected void onClickFolderIcon(View v) {
2692 if (LOGD) Log.d(TAG, "onClickFolder");
2693 if (!(v instanceof FolderIcon)){
2694 throw new IllegalArgumentException("Input must be a FolderIcon");
2695 }
2696
2697 FolderIcon folderIcon = (FolderIcon) v;
2698 final FolderInfo info = folderIcon.getFolderInfo();
2699 Folder openFolder = mWorkspace.getFolderForTag(info);
2700
2701 // If the folder info reports that the associated folder is open, then verify that
2702 // it is actually opened. There have been a few instances where this gets out of sync.
2703 if (info.opened && openFolder == null) {
2704 Log.d(TAG, "Folder info marked as open, but associated folder is not open. Screen: "
2705 + info.screenId + " (" + info.cellX + ", " + info.cellY + ")");
2706 info.opened = false;
2707 }
2708
2709 if (!info.opened && !folderIcon.getFolder().isDestroyed()) {
2710 // Close any open folder
2711 closeFolder();
2712 // Open the requested folder
2713 openFolder(folderIcon);
2714 } else {
2715 // Find the open folder...
2716 int folderScreen;
2717 if (openFolder != null) {
2718 folderScreen = mWorkspace.getPageForView(openFolder);
2719 // .. and close it
2720 closeFolder(openFolder);
2721 if (folderScreen != mWorkspace.getCurrentPage()) {
2722 // Close any folder open on the current screen
2723 closeFolder();
2724 // Pull the folder onto this screen
2725 openFolder(folderIcon);
2726 }
2727 }
2728 }
2729
2730 if (mLauncherCallbacks != null) {
2731 mLauncherCallbacks.onClickFolderIcon(v);
2732 }
2733 }
2734
2735 /**
2736 * Event handler for the (Add) Widgets button that appears after a long press
2737 * on the home screen.
2738 */
2739 protected void onClickAddWidgetButton(View view) {
2740 if (LOGD) {
2741 Log.d(TAG, "onClickAddWidgetButton");
2742 }
2743 if (mIsSafeModeEnabled) {
2744 Toast.makeText(this, R.string.safemode_widget_error, Toast.LENGTH_SHORT).show();
2745 } else {
2746 /* animated */
2747 /* resetPageToZero */
2748 showWidgetsView(true, true);
2749 if (mLauncherCallbacks != null) {
2750 mLauncherCallbacks.onClickAddWidgetButton(view);
2751 }
2752 }
2753 }
2754
2755 /**
2756 * Event handler for the wallpaper picker button that appears after a long press
2757 * on the home screen.
2758 */
2759 protected void onClickWallpaperPicker(View v) {
2760 if (LOGD) {
2761 Log.d(TAG, "onClickWallpaperPicker");
2762 }
2763 startActivityForResult(new Intent(Intent.ACTION_SET_WALLPAPER).setPackage(getPackageName()), REQU🔵
2764 if (mLauncherCallbacks != null) {
2765 mLauncherCallbacks.onClickWallpaperPicker(v);
2766 }
2767 }
2768
2769 /**
2770 * Event handler for a click on the settings button that appears after a long press
2771 * on the home screen.
2772 */
2773 protected void onClickSettingsButton(View v) {
2774 if (LOGD) Log.d(TAG, "onClickSettingsButton");
2775 if (mLauncherCallbacks != null) {
2776 mLauncherCallbacks.onClickSettingsButton(v);
2777 }
2778 }
2779
2780 public void onTouchDownAllAppsButton(View v) {
2781 // Provide the same haptic feedback that the system offers for virtual keys.
2782 v.performHapticFeedback(HapticFeedbackConstants.VIRTUAL_KEY);
2783 }
2784
2785 public void performHapticFeedbackOnTouchDown(View v) {
2786 // Provide the same haptic feedback that the system offers for virtual keys.
2787 v.performHapticFeedback(HapticFeedbackConstants.VIRTUAL_KEY);
2788 }
2789
2790 public View.OnTouchListener getHapticFeedbackTouchListener() {
2791 if (mHapticFeedbackTouchListener == null) {
2792 mHapticFeedbackTouchListener = new View.OnTouchListener() {
2793 @Override
2794 public boolean onTouch(View v, MotionEvent event) {
2795 if ((event.getAction() & MotionEvent.ACTION_MASK) == MotionEvent.ACTION_DOWN) {
2796 v.performHapticFeedback(HapticFeedbackConstants.VIRTUAL_KEY);
2797 }
2798 return false;
2799 }
2800 };
2801 }
2802 return mHapticFeedbackTouchListener;
2803 }
2804
2805 public void onDragStarted(View view) {
2806 if (isOnCustomContent()) {
2807 // Custom content screen doesn't participate in drag and drop. If on custom
2808 // content screen, move to default.
2809 moveWorkspaceToDefaultScreen();
2810 }
2811
2812 if (mLauncherCallbacks != null) {
2813 mLauncherCallbacks.onDragStarted(view);
2814 }
2815 }
2816
2817 /**
2818 * Called when the user stops interacting with the launcher.
2819 * This implies that the user is now on the homescreen and is not doing housekeeping.
2820 */
2821 protected void onInteractionEnd() {
2822 if (mLauncherCallbacks != null) {
2823 mLauncherCallbacks.onInteractionEnd();
2824 }
2825 }
2826
2827 /**
2828 * Called when the user starts interacting with the launcher.
2829 * The possible interactions are:
2830 * - open all apps
2831 * - reorder an app shortcut, or a widget
2832 * - open the overview mode.
2833 * This is a good time to stop doing things that only make sense
2834 * when the user is on the homescreen and not doing housekeeping.
2835 */
2836 protected void onInteractionBegin() {
2837 if (mLauncherCallbacks != null) {
2838 mLauncherCallbacks.onInteractionBegin();
2839 }
2840 }
2841
2842 void startApplicationDetailsActivity(ComponentName componentName, UserHandleCompat user) {
2843 try {
2844 LauncherAppsCompat launcherApps = LauncherAppsCompat.getInstance(this);
2845 launcherApps.showAppDetailsForProfile(componentName, user);
2846 } catch (SecurityException e) {
2847 Toast.makeText(this, R.string.activity_not_found, Toast.LENGTH_SHORT).show();
2848 Log.e(TAG, "Launcher does not have permission to launch settings");
2849 } catch (ActivityNotFoundException e) {
2850 Toast.makeText(this, R.string.activity_not_found, Toast.LENGTH_SHORT).show();
2851 Log.e(TAG, "Unable to launch settings");
2852 }
2853 }
2854
2855 // returns true if the activity was started
2856 boolean startApplicationUninstallActivity(ComponentName componentName, int flags,
2857 UserHandleCompat user) {
2858 if ((flags & AppInfo.DOWNLOADED_FLAG) == 0) {
2859 // System applications cannot be installed. For now, show a toast explaining that.
2860 // We may give them the option of disabling apps this way.
2861 int messageId = R.string.uninstall_system_app_text;
2862 Toast.makeText(this, messageId, Toast.LENGTH_SHORT).show();
2863 return false;
2864 } else {
2865 String packageName = componentName.getPackageName();
2866 String className = componentName.getClassName();
2867 Intent intent = new Intent(
2868 Intent.ACTION_DELETE, Uri.fromParts("package", packageName, className));
2869 intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK |
2870 Intent.FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS);
2871 if (user != null) {
2872 user.addToIntent(intent, Intent.EXTRA_USER);
2873 }
2874 startActivity(intent);
2875 return true;
2876 }
2877 }
2878
2879 boolean startActivity(View v, Intent intent, Object tag) {
2880 intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
2881 try {
2882 // Only launch using the new animation if the shortcut has not opted out (this is a
2883 // private contract between launcher and may be ignored in the future).
2884 boolean useLaunchAnimation = (v != null) && (!intent.hasExtra(INTENT_EXTRA_IGNORE_LAUNCH_ANIM🔵
2885 LauncherAppsCompat launcherApps = LauncherAppsCompat.getInstance(this);
2886 UserManagerCompat userManager = UserManagerCompat.getInstance(this);
2887 UserHandleCompat user = null;
2888 if (intent.hasExtra(AppInfo.EXTRA_PROFILE)) {
2889 long serialNumber = intent.getLongExtra(AppInfo.EXTRA_PROFILE, -1);
2890 user = userManager.getUserForSerialNumber(serialNumber);
2891 }
2892 Bundle optsBundle = null;
2893 if (useLaunchAnimation) {
2894 ActivityOptions opts = null;
2895 if (sClipRevealMethod != null) {
2896 // TODO: call method directly when Launcher3 can depend on M APIs
2897 int left = 0;
2898 int top = 0;
2899 int width = v.getMeasuredWidth();
2900 int height = v.getMeasuredHeight();
2901 if (v instanceof TextView) {
2902 // Launch from center of icon, not entire view
2903 Drawable icon = Workspace.getTextViewIcon(((TextView) (v)));
2904 if (icon != null) {
2905 Rect bounds = icon.getBounds();
2906 left = (width - bounds.width()) / 2;
2907 top = v.getPaddingTop();
2908 width = bounds.width();
2909 height = bounds.height();
2910 }
2911 }
2912 try {
2913 opts = ((ActivityOptions) (sClipRevealMethod.invoke(null, v, left, top, width, he🔵
2914 } catch (java.lang.IllegalAccessException e) {
2915 Log.d(TAG, "Could not call makeClipRevealAnimation: " + e);
2916 sClipRevealMethod = null;
2917 } catch (InvocationTargetException e) {
2918 Log.d(TAG, "Could not call makeClipRevealAnimation: " + e);
2919 sClipRevealMethod = null;
2920 }
2921 }
2922 if ((opts == null) && (!Utilities.isLmpOrAbove())) {
2923 opts = ActivityOptions.makeScaleUpAnimation(v, 0, 0, v.getMeasuredWidth(), v.getMeasu🔵
2924 }
2925 optsBundle = (opts != null) ? opts.toBundle() : null;
2926 }
2927 if ((user == null) || user.equals(UserHandleCompat.myUserHandle())) {
2928 // Could be launching some bookkeeping activity
2929 startActivity(intent, optsBundle);
2930 } else {
2931 // TODO Component can be null when shortcuts are supported for secondary user
2932 launcherApps.startActivityForProfile(intent.getComponent(), user, intent.getSourceBounds(🔵
2933 }
2934 return true;
2935 } catch (java.lang.SecurityException e) {
2936 Toast.makeText(this, R.string.activity_not_found, Toast.LENGTH_SHORT).show();
2937 Log.e(TAG, (((((("Launcher does not have the permission to launch " + intent) + ". Make sure 🔵
2938 }
2939 return false;
2940 }
2941
2942 boolean startActivitySafely(View v, Intent intent, Object tag) {
2943 boolean success = false;
2944 if (mIsSafeModeEnabled && !Utilities.isSystemApp(this, intent)) {
2945 Toast.makeText(this, R.string.safemode_shortcut_error, Toast.LENGTH_SHORT).show();
2946 return false;
2947 }
2948 try {
2949 success = startActivity(v, intent, tag);
2950 } catch (ActivityNotFoundException e) {
2951 Toast.makeText(this, R.string.activity_not_found, Toast.LENGTH_SHORT).show();
2952 Log.e(TAG, "Unable to launch. tag=" + tag + " intent=" + intent, e);
2953 }
2954 return success;
2955 }
2956
2957 /**
2958 * This method draws the FolderIcon to an ImageView and then adds and positions that ImageView
2959 * in the DragLayer in the exact absolute location of the original FolderIcon.
2960 */
2961 private void copyFolderIconToImage(FolderIcon fi) {
2962 final int width = fi.getMeasuredWidth();
2963 final int height = fi.getMeasuredHeight();
2964
2965 // Lazy load ImageView, Bitmap and Canvas
2966 if (mFolderIconImageView == null) {
2967 mFolderIconImageView = new ImageView(this);
2968 }
2969 if (mFolderIconBitmap == null || mFolderIconBitmap.getWidth() != width ||
2970 mFolderIconBitmap.getHeight() != height) {
2971 mFolderIconBitmap = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888);
2972 mFolderIconCanvas = new Canvas(mFolderIconBitmap);
2973 }
2974
2975 DragLayer.LayoutParams lp;
2976 if (mFolderIconImageView.getLayoutParams() instanceof DragLayer.LayoutParams) {
2977 lp = (DragLayer.LayoutParams) mFolderIconImageView.getLayoutParams();
2978 } else {
2979 lp = new DragLayer.LayoutParams(width, height);
2980 }
2981
2982 // The layout from which the folder is being opened may be scaled, adjust the starting
2983 // view size by this scale factor.
2984 float scale = mDragLayer.getDescendantRectRelativeToSelf(fi, mRectForFolderAnimation);
2985 lp.customPosition = true;
2986 lp.x = mRectForFolderAnimation.left;
2987 lp.y = mRectForFolderAnimation.top;
2988 lp.width = (int) (scale * width);
2989 lp.height = (int) (scale * height);
2990
2991 mFolderIconCanvas.drawColor(0, PorterDuff.Mode.CLEAR);
2992 fi.draw(mFolderIconCanvas);
2993 mFolderIconImageView.setImageBitmap(mFolderIconBitmap);
2994 if (fi.getFolder() != null) {
2995 mFolderIconImageView.setPivotX(fi.getFolder().getPivotXForIconAnimation());
2996 mFolderIconImageView.setPivotY(fi.getFolder().getPivotYForIconAnimation());
2997 }
2998 // Just in case this image view is still in the drag layer from a previous animation,
2999 // we remove it and re-add it.
3000 if (mDragLayer.indexOfChild(mFolderIconImageView) != -1) {
3001 mDragLayer.removeView(mFolderIconImageView);
3002 }
3003 mDragLayer.addView(mFolderIconImageView, lp);
3004 if (fi.getFolder() != null) {
3005 fi.getFolder().bringToFront();
3006 }
3007 }
3008
3009 private void growAndFadeOutFolderIcon(FolderIcon fi) {
3010 if (fi == null) return;
3011 PropertyValuesHolder alpha = PropertyValuesHolder.ofFloat("alpha", 0);
3012 PropertyValuesHolder scaleX = PropertyValuesHolder.ofFloat("scaleX", 1.5f);
3013 PropertyValuesHolder scaleY = PropertyValuesHolder.ofFloat("scaleY", 1.5f);
3014
3015 FolderInfo info = (FolderInfo) fi.getTag();
3016 if (info.container == LauncherSettings.Favorites.CONTAINER_HOTSEAT) {
3017 CellLayout cl = (CellLayout) fi.getParent().getParent();
3018 CellLayout.LayoutParams lp = (CellLayout.LayoutParams) fi.getLayoutParams();
3019 cl.setFolderLeaveBehindCell(lp.cellX, lp.cellY);
3020 }
3021
3022 // Push an ImageView copy of the FolderIcon into the DragLayer and hide the original
3023 copyFolderIconToImage(fi);
3024 fi.setVisibility(View.INVISIBLE);
3025
3026 ObjectAnimator oa = LauncherAnimUtils.ofPropertyValuesHolder(mFolderIconImageView, alpha,
3027 scaleX, scaleY);
3028 if (Utilities.isLmpOrAbove()) {
3029 oa.setInterpolator(new LogDecelerateInterpolator(100, 0));
3030 }
3031 oa.setDuration(getResources().getInteger(R.integer.config_folderExpandDuration));
3032 oa.start();
3033 }
3034
3035 private void shrinkAndFadeInFolderIcon(final FolderIcon fi) {
3036 if (fi == null) return;
3037 PropertyValuesHolder alpha = PropertyValuesHolder.ofFloat("alpha", 1.0f);
3038 PropertyValuesHolder scaleX = PropertyValuesHolder.ofFloat("scaleX", 1.0f);
3039 PropertyValuesHolder scaleY = PropertyValuesHolder.ofFloat("scaleY", 1.0f);
3040
3041 final CellLayout cl = (CellLayout) fi.getParent().getParent();
3042
3043 // We remove and re-draw the FolderIcon in-case it has changed
3044 mDragLayer.removeView(mFolderIconImageView);
3045 copyFolderIconToImage(fi);
3046 ObjectAnimator oa = LauncherAnimUtils.ofPropertyValuesHolder(mFolderIconImageView, alpha,
3047 scaleX, scaleY);
3048 oa.setDuration(getResources().getInteger(R.integer.config_folderExpandDuration));
3049 oa.addListener(new AnimatorListenerAdapter() {
3050 @Override
3051 public void onAnimationEnd(Animator animation) {
3052 if (cl != null) {
3053 cl.clearFolderLeaveBehind();
3054 // Remove the ImageView copy of the FolderIcon and make the original visible.
3055 mDragLayer.removeView(mFolderIconImageView);
3056 fi.setVisibility(View.VISIBLE);
3057 }
3058 }
3059 });
3060 oa.start();
3061 }
3062
3063 /**
3064 * Opens the user folder described by the specified tag. The opening of the folder
3065 * is animated relative to the specified View. If the View is null, no animation
3066 * is played.
3067 *
3068 * @param folderInfo The FolderInfo describing the folder to open.
3069 */
3070 public void openFolder(FolderIcon folderIcon) {
3071 Folder folder = folderIcon.getFolder();
3072 Folder openFolder = (mWorkspace != null) ? mWorkspace.getOpenFolder() : null;
3073 if ((openFolder != null) && (openFolder != folder)) {
3074 // Close any open folder before opening a folder.
3075 closeFolder();
3076 }
3077 FolderInfo info = folder.mInfo;
3078 info.opened = true;
3079 // While the folder is open, the position of the icon cannot change.
3080 ((CellLayout.LayoutParams) (folderIcon.getLayoutParams())).canReorder = false;
3081 // Just verify that the folder hasn't already been added to the DragLayer.
3082 // There was a one-off crash where the folder had a parent already.
3083 if (folder.getParent() == null) {
3084 mDragLayer.addView(folder);
3085 mDragController.addDropTarget(((DropTarget) (folder)));
3086 } else {
3087 Log.w(TAG, ((("Opening folder (" + folder) + ") which already has a parent (") + folder.getPa🔵
3088 }
3089 folder.animateOpen();
3090 growAndFadeOutFolderIcon(folderIcon);
3091 // Notify the accessibility manager that this folder "window" has appeared and occluded
3092 // the workspace items
3093 folder.sendAccessibilityEvent(AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED);
3094 getDragLayer().sendAccessibilityEvent(AccessibilityEvent.TYPE_WINDOW_CONTENT_CHANGED);
3095 }
3096
3097 public void closeFolder() {
3098 Folder folder = mWorkspace != null ? mWorkspace.getOpenFolder() : null;
3099 if (folder != null) {
3100 if (folder.isEditingName()) {
3101 folder.dismissEditingName();
3102 }
3103 closeFolder(folder);
3104 }
3105 }
3106
3107 void closeFolder(Folder folder) {
3108 folder.getInfo().opened = false;
3109 ViewGroup parent = ((ViewGroup) (folder.getParent().getParent()));
3110 if (parent != null) {
3111 FolderIcon fi = ((FolderIcon) (mWorkspace.getViewForTag(folder.mInfo)));
3112 shrinkAndFadeInFolderIcon(fi);
3113 if (fi != null) {
3114 ((CellLayout.LayoutParams) (fi.getLayoutParams())).canReorder = true;
3115 }
3116 }
3117 folder.animateClosed();
3118 // Notify the accessibility manager that this folder "window" has disappeard and no
3119 // longer occludeds the workspace items
3120 getDragLayer().sendAccessibilityEvent(AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED);
3121 }
3122
3123 public boolean onLongClick(View v) {
3124 if (!isDraggingEnabled()) return false;
3125 if (isWorkspaceLocked()) return false;
3126 if (mState != State.WORKSPACE) return false;
3127
3128 if (v instanceof Workspace) {
3129 if (!mWorkspace.isInOverviewMode()) {
3130 if (mWorkspace.enterOverviewMode()) {
3131 mWorkspace.performHapticFeedback(HapticFeedbackConstants.LONG_PRESS,
3132 HapticFeedbackConstants.FLAG_IGNORE_VIEW_SETTING);
3133 return true;
3134 } else {
3135 return false;
3136 }
3137 } else {
3138 return false;
3139 }
3140 }
3141
3142 CellLayout.CellInfo longClickCellInfo = null;
3143 View itemUnderLongClick = null;
3144 if (v.getTag() instanceof ItemInfo) {
3145 ItemInfo info = (ItemInfo) v.getTag();
3146 longClickCellInfo = new CellLayout.CellInfo(v, info);;
3147 itemUnderLongClick = longClickCellInfo.cell;
3148 resetAddInfo();
3149 }
3150
3151 // The hotseat touch handling does not go through Workspace, and we always allow long press
3152 // on hotseat items.
3153 final boolean inHotseat = isHotseatLayout(v);
3154 boolean allowLongPress = inHotseat || mWorkspace.allowLongPress();
3155 if (allowLongPress && !mDragController.isDragging()) {
3156 if (itemUnderLongClick == null) {
3157 // User long pressed on empty space
3158 mWorkspace.performHapticFeedback(HapticFeedbackConstants.LONG_PRESS,
3159 HapticFeedbackConstants.FLAG_IGNORE_VIEW_SETTING);
3160 if (mWorkspace.isInOverviewMode()) {
3161 mWorkspace.startReordering(v);
3162 } else {
3163 mWorkspace.enterOverviewMode();
3164 }
3165 } else {
3166 final boolean isAllAppsButton = inHotseat && isAllAppsButtonRank(
3167 mHotseat.getOrderInHotseat(
3168 longClickCellInfo.cellX,
3169 longClickCellInfo.cellY));
3170 if (!(itemUnderLongClick instanceof Folder || isAllAppsButton)) {
3171 // User long pressed on an item
3172 mWorkspace.startDrag(longClickCellInfo);
3173 }
3174 }
3175 }
3176 return true;
3177 }
3178
3179 boolean isHotseatLayout(View layout) {
3180 return mHotseat != null && layout != null &&
3181 (layout instanceof CellLayout) && (layout == mHotseat.getLayout());
3182 }
3183
3184 /**
3185 * Returns the CellLayout of the specified container at the specified screen.
3186 */
3187 public CellLayout getCellLayout(long container, long screenId) {
3188 if (container == LauncherSettings.Favorites.CONTAINER_HOTSEAT) {
3189 if (mHotseat != null) {
3190 return mHotseat.getLayout();
3191 } else {
3192 return null;
3193 }
3194 } else {
3195 return mWorkspace.getScreenWithId(screenId);
3196 }
3197 }
3198
3199 /**
3200 * For overridden classes.
3201 */
3202 public boolean isAllAppsVisible() {
3203 return isAppsViewVisible();
3204 }
3205
3206 public boolean isAppsViewVisible() {
3207 return (mState == State.APPS) || (mOnResumeState == State.APPS);
3208 }
3209
3210 public boolean isWidgetsViewVisible() {
3211 return (mState == State.WIDGETS) || (mOnResumeState == State.WIDGETS);
3212 }
3213
3214 private void setWorkspaceBackground(boolean workspace) {
3215 mLauncherView.setBackground(workspace ?
3216 mWorkspaceBackgroundDrawable : null);
3217 }
3218
3219 protected void changeWallpaperVisiblity(boolean visible) {
3220 int wpflags = visible ? WindowManager.LayoutParams.FLAG_SHOW_WALLPAPER : 0;
3221 int curflags = getWindow().getAttributes().flags
3222 & WindowManager.LayoutParams.FLAG_SHOW_WALLPAPER;
3223 if (wpflags != curflags) {
3224 getWindow().setFlags(wpflags, WindowManager.LayoutParams.FLAG_SHOW_WALLPAPER);
3225 }
3226 setWorkspaceBackground(visible);
3227 }
3228
3229 @Override
3230 public void onTrimMemory(int level) {
3231 super.onTrimMemory(level);
3232 if (level >= ComponentCallbacks2.TRIM_MEMORY_UI_HIDDEN) {
3233 // The widget preview db can result in holding onto over
3234 // 3MB of memory for caching which isn't necessary.
3235 SQLiteDatabase.releaseMemory();
3236 // This clears all widget bitmaps from the widget tray
3237 // TODO(hyunyoungs)
3238 }
3239 if (mLauncherCallbacks != null) {
3240 mLauncherCallbacks.onTrimMemory(level);
3241 }
3242 }
3243
3244 @Override
3245 public void onStateTransitionHideSearchBar() {
3246 // Hide the search bar
3247 if (mSearchDropTargetBar != null) {
3248 /* animated */
3249 mSearchDropTargetBar.hideSearchBar(false);
3250 }
3251 }
3252
3253 protected void showWorkspace(boolean animated) {
3254 showWorkspace(animated, null);
3255 }
3256
3257 void showWorkspace(boolean animated, Runnable onCompleteRunnable) {
3258 boolean changed = (mState != State.WORKSPACE) || (mWorkspace.getState() != Workspace.State.NORMAL🔵
3259 if (changed) {
3260 boolean wasInSpringLoadedMode = mState != State.WORKSPACE;
3261 mWorkspace.setVisibility(View.VISIBLE);
3262 mStateTransitionAnimation.startAnimationToWorkspace(mState, Workspace.State.NORMAL, animated,🔵
3263 // Show the search bar (only animate if we were showing the drop target bar in spring
3264 // loaded mode)
3265 if (mSearchDropTargetBar != null) {
3266 mSearchDropTargetBar.showSearchBar(animated && wasInSpringLoadedMode);
3267 }
3268 // Set focus to the AppsCustomize button
3269 if (mAllAppsButton != null) {
3270 mAllAppsButton.requestFocus();
3271 }
3272 }
3273 // Change the state *after* we've called all the transition code
3274 mState = State.WORKSPACE;
3275 // Resume the auto-advance of widgets
3276 mUserPresent = true;
3277 updateAutoAdvanceState();
3278 if (changed) {
3279 // Send an accessibility event to announce the context change
3280 getWindow().getDecorView().sendAccessibilityEvent(AccessibilityEvent.TYPE_WINDOW_STATE_CHANGE🔵
3281 onWorkspaceShown(animated);
3282 }
3283 }
3284
3285 void showOverviewMode(boolean animated) {
3286 mWorkspace.setVisibility(View.VISIBLE);
3287 /* onCompleteRunnable */
3288 mStateTransitionAnimation.startAnimationToWorkspace(mState, Workspace.State.OVERVIEW, animated, n🔵
3289 mState = State.WORKSPACE;
3290 onWorkspaceShown(animated);
3291 }
3292
3293 public void onWorkspaceShown(boolean animated) {
3294 }
3295
3296 /**
3297 * Shows the apps view.
3298 */
3299 void showAppsView(boolean animated, boolean resetListToTop) {
3300 if (resetListToTop) {
3301 mAppsView.scrollToTop();
3302 }
3303 showAppsOrWidgets(animated, State.APPS);
3304 }
3305
3306 /**
3307 * Shows the widgets view.
3308 */
3309 void showWidgetsView(boolean animated, boolean resetPageToZero) {
3310 Log.d(TAG, (("showWidgetsView:" + animated) + " resetPageToZero:") + resetPageToZero);
3311 if (resetPageToZero) {
3312 mWidgetsView.scrollToTop();
3313 }
3314 showAppsOrWidgets(animated, State.WIDGETS);
3315 mWidgetsView.post(new Runnable() {
3316 @Override
3317 public void run() {
3318 mWidgetsView.requestFocus();
3319 }
3320 });
3321 }
3322
3323 /**
3324 * Sets up the transition to show the apps/widgets view.
3325 */
3326 private void showAppsOrWidgets(boolean animated, State toState) {
3327 if (mState != State.WORKSPACE) {
3328 return;
3329 }
3330 if ((toState != State.APPS) && (toState != State.WIDGETS)) {
3331 return;
3332 }
3333 if (toState == State.APPS) {
3334 mStateTransitionAnimation.startAnimationToAllApps(animated);
3335 if (mLauncherCallbacks != null) {
3336 mLauncherCallbacks.onAllAppsShown();
3337 }
3338 } else {
3339 mStateTransitionAnimation.startAnimationToWidgets(animated);
3340 }
3341 // Change the state *after* we've called all the transition code
3342 mState = toState;
3343 // Pause the auto-advance of widgets until we are out of AllApps
3344 mUserPresent = false;
3345 updateAutoAdvanceState();
3346 closeFolder();
3347 // Send an accessibility event to announce the context change
3348 getWindow().getDecorView().sendAccessibilityEvent(AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED);
3349 }
3350
3351 public void enterSpringLoadedDragMode() {
3352 Log.d(TAG, String.format("enterSpringLoadedDragMode [mState=%s", mState.name()));
3353 if (((mState == State.WORKSPACE) || (mState == State.APPS_SPRING_LOADED)) || (mState == State.WID🔵
3354 return;
3355 }
3356 /* animated */
3357 /* onCompleteRunnable */
3358 mStateTransitionAnimation.startAnimationToWorkspace(mState, Workspace.State.SPRING_LOADED, true, 🔵
3359 mState = (isAppsViewVisible()) ? State.APPS_SPRING_LOADED : State.WIDGETS_SPRING_LOADED;
3360 }
3361
3362 public void exitSpringLoadedDragModeDelayed(final boolean successfulDrop, int delay, final Runnable o🔵
3363 if ((mState != State.APPS_SPRING_LOADED) && (mState != State.WIDGETS_SPRING_LOADED)) {
3364 return;
3365 }
3366 mHandler.postDelayed(new Runnable() {
3367 @Override
3368 public void run() {
3369 if (successfulDrop) {
3370 // TODO(hyunyoungs): verify if this hack is still needed, if not, delete.
3371 //
3372 // Before we show workspace, hide all apps again because
3373 // exitSpringLoadedDragMode made it visible. This is a bit hacky; we should
3374 // clean up our state transition functions
3375 mWidgetsView.setVisibility(View.GONE);
3376 showWorkspace(true, onCompleteRunnable);
3377 } else {
3378 exitSpringLoadedDragMode();
3379 }
3380 }
3381 }, delay);
3382 }
3383
3384 void exitSpringLoadedDragMode() {
3385 if (mState == State.APPS_SPRING_LOADED) {
3386 /* animated */
3387 mStateTransitionAnimation.startAnimationToAllApps(true);
3388 mState = State.APPS;
3389 } else if (mState == State.WIDGETS_SPRING_LOADED) {
3390 /* animated */
3391 mStateTransitionAnimation.startAnimationToWidgets(true);
3392 mState = State.WIDGETS;
3393 }
3394 // Otherwise, we are not in spring loaded mode, so don't do anything.
3395 }
3396
3397 void lockAllApps() {
3398 // TODO
3399 }
3400
3401 void unlockAllApps() {
3402 // TODO
3403 }
3404
3405 protected void disableVoiceButtonProxy(boolean disable) {
3406 // NO-OP
3407 }
3408
3409 public View getOrCreateQsbBar() {
3410 if ((mLauncherCallbacks != null) && mLauncherCallbacks.providesSearch()) {
3411 return mLauncherCallbacks.getQsbBar();
3412 }
3413 if (mQsb == null) {
3414 AppWidgetProviderInfo searchProvider = Utilities.getSearchWidgetProvider(this);
3415 if (searchProvider == null) {
3416 return null;
3417 }
3418 Bundle opts = new Bundle();
3419 opts.putInt(AppWidgetManager.OPTION_APPWIDGET_HOST_CATEGORY, AppWidgetProviderInfo.WIDGET_CAT🔵
3420 SharedPreferences sp = getSharedPreferences(LauncherAppState.getSharedPreferencesKey(), MODE_🔵
3421 int widgetId = sp.getInt(QSB_WIDGET_ID, -1);
3422 AppWidgetProviderInfo widgetInfo = mAppWidgetManager.getAppWidgetInfo(widgetId);
3423 if (((!searchProvider.provider.flattenToString().equals(sp.getString(QSB_WIDGET_PROVIDER, nul🔵
3424 // A valid widget is not already bound.
3425 if (widgetId > (-1)) {
3426 mAppWidgetHost.deleteAppWidgetId(widgetId);
3427 widgetId = -1;
3428 }
3429 // Try to bind a new widget
3430 widgetId = mAppWidgetHost.allocateAppWidgetId();
3431 if (!AppWidgetManagerCompat.getInstance(this).bindAppWidgetIdIfAllowed(widgetId, searchPr🔵
3432 mAppWidgetHost.deleteAppWidgetId(widgetId);
3433 widgetId = -1;
3434 }
3435 sp.edit().putInt(QSB_WIDGET_ID, widgetId).putString(QSB_WIDGET_PROVIDER, searchProvider.p🔵
3436 }
3437 if (widgetId != (-1)) {
3438 mQsb = mAppWidgetHost.createView(this, widgetId, searchProvider);
3439 mQsb.updateAppWidgetOptions(opts);
3440 mQsb.setPadding(0, 0, 0, 0);
3441 mSearchDropTargetBar.addView(mQsb);
3442 mSearchDropTargetBar.setQsbSearchBar(mQsb);
3443 }
3444 }
3445 return mQsb;
3446 }
3447
3448 private void reinflateQSBIfNecessary() {
3449 if ((mQsb instanceof LauncherAppWidgetHostView) && ((LauncherAppWidgetHostView) (mQsb)).isReinfla🔵
3450 mSearchDropTargetBar.removeView(mQsb);
3451 mQsb = null;
3452 mSearchDropTargetBar.setQsbSearchBar(getOrCreateQsbBar());
3453 }
3454 }
3455
3456 @Override
3457 public boolean dispatchPopulateAccessibilityEvent(AccessibilityEvent event) {
3458 final boolean result = super.dispatchPopulateAccessibilityEvent(event);
3459 final List<CharSequence> text = event.getText();
3460 text.clear();
3461 // Populate event with a fake title based on the current state.
3462 if (mState == State.APPS) {
3463 text.add("Apps");
3464 } else if (mState == State.WIDGETS) {
3465 text.add("Widgets");
3466 } else {
3467 text.add(getString(R.string.all_apps_home_button_label));
3468 }
3469 return result;
3470 }
3471
3472 /**
3473 * Receives notifications when system dialogs are to be closed.
3474 */
3475 @Thunk
3476 class CloseSystemDialogsIntentReceiver extends BroadcastReceiver {
3477 @Override
3478 public void onReceive(Context context, Intent intent) {
3479 closeSystemDialogs();
3480 }
3481 }
3482
3483 /**
3484 * Receives notifications whenever the appwidgets are reset.
3485 */
3486 private class AppWidgetResetObserver extends ContentObserver {
3487 public AppWidgetResetObserver() {
3488 super(new Handler());
3489 }
3490
3491 @Override
3492 public void onChange(boolean selfChange) {
3493 onAppWidgetReset();
3494 }
3495 }
3496
3497 /**
3498 * If the activity is currently paused, signal that we need to run the passed Runnable
3499 * in onResume.
3500 *
3501 * This needs to be called from incoming places where resources might have been loaded
3502 * while we are paused. That is becaues the Configuration might be wrong
3503 * when we're not running, and if it comes back to what it was when we
3504 * were paused, we are not restarted.
3505 *
3506 * Implementation of the method from LauncherModel.Callbacks.
3507 *
3508 * @return true if we are currently paused. The caller might be able to
3509 * skip some work in that case since we will come back again.
3510 */
3511 private boolean waitUntilResume(Runnable run, boolean deletePreviousRunnables) {
3512 if (mPaused) {
3513 Log.i(TAG, "Deferring update until onResume");
3514 if (deletePreviousRunnables) {
3515 while (mBindOnResumeCallbacks.remove(run)) {
3516 }
3517 }
3518 mBindOnResumeCallbacks.add(run);
3519 return true;
3520 } else {
3521 return false;
3522 }
3523 }
3524
3525 private boolean waitUntilResume(Runnable run) {
3526 return waitUntilResume(run, false);
3527 }
3528
3529 public void addOnResumeCallback(Runnable run) {
3530 mOnResumeCallbacks.add(run);
3531 }
3532
3533 /**
3534 * If the activity is currently paused, signal that we need to re-run the loader
3535 * in onResume.
3536 *
3537 * This needs to be called from incoming places where resources might have been loaded
3538 * while we are paused. That is becaues the Configuration might be wrong
3539 * when we're not running, and if it comes back to what it was when we
3540 * were paused, we are not restarted.
3541 *
3542 * Implementation of the method from LauncherModel.Callbacks.
3543 *
3544 * @return true if we are currently paused. The caller might be able to
3545 * skip some work in that case since we will come back again.
3546 */
3547 public boolean setLoadOnResume() {
3548 if (mPaused) {
3549 Log.i(TAG, "setLoadOnResume");
3550 mOnResumeNeedsLoad = true;
3551 return true;
3552 } else {
3553 return false;
3554 }
3555 }
3556
3557 /**
3558 * Implementation of the method from LauncherModel.Callbacks.
3559 */
3560 public int getCurrentWorkspaceScreen() {
3561 if (mWorkspace != null) {
3562 return mWorkspace.getCurrentPage();
3563 } else {
3564 return SCREEN_COUNT / 2;
3565 }
3566 }
3567
3568 /**
3569 * Refreshes the shortcuts shown on the workspace.
3570 *
3571 * Implementation of the method from LauncherModel.Callbacks.
3572 */
3573 public void startBinding() {
3574 setWorkspaceLoading(true);
3575
3576 // If we're starting binding all over again, clear any bind calls we'd postponed in
3577 // the past (see waitUntilResume) -- we don't need them since we're starting binding
3578 // from scratch again
3579 mBindOnResumeCallbacks.clear();
3580
3581 // Clear the workspace because it's going to be rebound
3582 mWorkspace.clearDropTargets();
3583 mWorkspace.removeAllWorkspaceScreens();
3584
3585 mWidgetsToAdvance.clear();
3586 if (mHotseat != null) {
3587 mHotseat.resetLayout();
3588 }
3589 }
3590
3591 @Override
3592 public void bindScreens(ArrayList<Long> orderedScreenIds) {
3593 bindAddScreens(orderedScreenIds);
3594
3595 // If there are no screens, we need to have an empty screen
3596 if (orderedScreenIds.size() == 0) {
3597 mWorkspace.addExtraEmptyScreen();
3598 }
3599
3600 // Create the custom content page (this call updates mDefaultScreen which calls
3601 // setCurrentPage() so ensure that all pages are added before calling this).
3602 if (hasCustomContentToLeft()) {
3603 mWorkspace.createCustomContentContainer();
3604 populateCustomContentContainer();
3605 }
3606 }
3607
3608 @Override
3609 public void bindAddScreens(ArrayList<Long> orderedScreenIds) {
3610 // Log to disk
3611 Launcher.addDumpLog(TAG, "11683562 - bindAddScreens()", true);
3612 Launcher.addDumpLog(TAG, "11683562 - orderedScreenIds: " +
3613 TextUtils.join(", ", orderedScreenIds), true);
3614 int count = orderedScreenIds.size();
3615 for (int i = 0; i < count; i++) {
3616 mWorkspace.insertNewWorkspaceScreenBeforeEmptyScreen(orderedScreenIds.get(i));
3617 }
3618 }
3619
3620 @Override
3621 public void bindAddPendingItem(final PendingAddItemInfo info, final long container,
3622 final long screenId, final int[] cell, final int spanX, final int spanY) {
3623 showWorkspace(true, new Runnable() {
3624
3625 @Override
3626 public void run() {
3627 mWorkspace.snapToPage(mWorkspace.getPageIndexForScreenId(screenId));
3628 addPendingItem(info, container, screenId, cell, spanX, spanY);
3629 }
3630 });
3631 }
3632
3633 private boolean shouldShowWeightWatcher() {
3634 String spKey = LauncherAppState.getSharedPreferencesKey();
3635 SharedPreferences sp = getSharedPreferences(spKey, Context.MODE_PRIVATE);
3636 boolean show = sp.getBoolean(SHOW_WEIGHT_WATCHER, SHOW_WEIGHT_WATCHER_DEFAULT);
3637
3638 return show;
3639 }
3640
3641 private void toggleShowWeightWatcher() {
3642 String spKey = LauncherAppState.getSharedPreferencesKey();
3643 SharedPreferences sp = getSharedPreferences(spKey, Context.MODE_PRIVATE);
3644 boolean show = sp.getBoolean(SHOW_WEIGHT_WATCHER, true);
3645
3646 show = !show;
3647
3648 SharedPreferences.Editor editor = sp.edit();
3649 editor.putBoolean(SHOW_WEIGHT_WATCHER, show);
3650 editor.commit();
3651
3652 if (mWeightWatcher != null) {
3653 mWeightWatcher.setVisibility(show ? View.VISIBLE : View.GONE);
3654 }
3655 }
3656
3657 public void bindAppsAdded(final ArrayList<Long> newScreens, final ArrayList<ItemInfo> addNotAnimated,🔵
3658 Runnable r = new Runnable() {
3659 public void run() {
3660 bindAppsAdded(newScreens, addNotAnimated, addAnimated, addedApps);
3661 }
3662 };
3663 if (waitUntilResume(r)) {
3664 return;
3665 }
3666 // Add the new screens
3667 if (newScreens != null) {
3668 bindAddScreens(newScreens);
3669 }
3670 // We add the items without animation on non-visible pages, and with
3671 // animations on the new page (which we will try and snap to).
3672 if ((addNotAnimated != null) && (!addNotAnimated.isEmpty())) {
3673 bindItems(addNotAnimated, 0, addNotAnimated.size(), false);
3674 }
3675 if ((addAnimated != null) && (!addAnimated.isEmpty())) {
3676 bindItems(addAnimated, 0, addAnimated.size(), true);
3677 }
3678 // Remove the extra empty screen
3679 mWorkspace.removeExtraEmptyScreen(false, false);
3680 if ((addedApps != null) && (mAppsView != null)) {
3681 mAppsView.addApps(addedApps);
3682 }
3683 }
3684
3685 /**
3686 * Bind the items start-end from the list.
3687 *
3688 * Implementation of the method from LauncherModel.Callbacks.
3689 */
3690 public void bindItems(final ArrayList<ItemInfo> shortcuts, final int start, final int end,
3691 final boolean forceAnimateIcons) {
3692 Runnable r = new Runnable() {
3693 public void run() {
3694 bindItems(shortcuts, start, end, forceAnimateIcons);
3695 }
3696 };
3697 if (waitUntilResume(r)) {
3698 return;
3699 }
3700
3701 // Get the list of added shortcuts and intersect them with the set of shortcuts here
3702 final AnimatorSet anim = LauncherAnimUtils.createAnimatorSet();
3703 final Collection<Animator> bounceAnims = new ArrayList<Animator>();
3704 final boolean animateIcons = forceAnimateIcons && canRunNewAppsAnimation();
3705 Workspace workspace = mWorkspace;
3706 long newShortcutsScreenId = -1;
3707 for (int i = start; i < end; i++) {
3708 final ItemInfo item = shortcuts.get(i);
3709
3710 // Short circuit if we are loading dock items for a configuration which has no dock
3711 if (item.container == LauncherSettings.Favorites.CONTAINER_HOTSEAT &&
3712 mHotseat == null) {
3713 continue;
3714 }
3715
3716 switch (item.itemType) {
3717 case LauncherSettings.Favorites.ITEM_TYPE_APPLICATION:
3718 case LauncherSettings.Favorites.ITEM_TYPE_SHORTCUT:
3719 ShortcutInfo info = (ShortcutInfo) item;
3720 View shortcut = createShortcut(info);
3721
3722 /*
3723 * TODO: FIX collision case
3724 */
3725 if (item.container == LauncherSettings.Favorites.CONTAINER_DESKTOP) {
3726 CellLayout cl = mWorkspace.getScreenWithId(item.screenId);
3727 if (cl != null && cl.isOccupied(item.cellX, item.cellY)) {
3728 View v = cl.getChildAt(item.cellX, item.cellY);
3729 Object tag = v.getTag();
3730 String desc = "Collision while binding workspace item: " + item
3731 + ". Collides with " + tag;
3732 if (LauncherAppState.isDogfoodBuild()) {
3733 throw (new RuntimeException(desc));
3734 } else {
3735 Log.d(TAG, desc);
3736 }
3737 }
3738 }
3739
3740 workspace.addInScreenFromBind(shortcut, item.container, item.screenId, item.cellX,
3741 item.cellY, 1, 1);
3742 if (animateIcons) {
3743 // Animate all the applications up now
3744 shortcut.setAlpha(0f);
3745 shortcut.setScaleX(0f);
3746 shortcut.setScaleY(0f);
3747 bounceAnims.add(createNewAppBounceAnimation(shortcut, i));
3748 newShortcutsScreenId = item.screenId;
3749 }
3750 break;
3751 case LauncherSettings.Favorites.ITEM_TYPE_FOLDER:
3752 FolderIcon newFolder = FolderIcon.fromXml(R.layout.folder_icon, this,
3753 (ViewGroup) workspace.getChildAt(workspace.getCurrentPage()),
3754 (FolderInfo) item, mIconCache);
3755 workspace.addInScreenFromBind(newFolder, item.container, item.screenId, item.cellX,
3756 item.cellY, 1, 1);
3757 break;
3758 default:
3759 throw new RuntimeException("Invalid Item Type");
3760 }
3761 }
3762
3763 if (animateIcons) {
3764 // Animate to the correct page
3765 if (newShortcutsScreenId > -1) {
3766 long currentScreenId = mWorkspace.getScreenIdForPageIndex(mWorkspace.getNextPage());
3767 final int newScreenIndex = mWorkspace.getPageIndexForScreenId(newShortcutsScreenId);
3768 final Runnable startBounceAnimRunnable = new Runnable() {
3769 public void run() {
3770 anim.playTogether(bounceAnims);
3771 anim.start();
3772 }
3773 };
3774 if (newShortcutsScreenId != currentScreenId) {
3775 // We post the animation slightly delayed to prevent slowdowns
3776 // when we are loading right after we return to launcher.
3777 mWorkspace.postDelayed(new Runnable() {
3778 public void run() {
3779 if (mWorkspace != null) {
3780 mWorkspace.snapToPage(newScreenIndex);
3781 mWorkspace.postDelayed(startBounceAnimRunnable,
3782 NEW_APPS_ANIMATION_DELAY);
3783 }
3784 }
3785 }, NEW_APPS_PAGE_MOVE_DELAY);
3786 } else {
3787 mWorkspace.postDelayed(startBounceAnimRunnable, NEW_APPS_ANIMATION_DELAY);
3788 }
3789 }
3790 }
3791 workspace.requestLayout();
3792 }
3793
3794 /**
3795 * Implementation of the method from LauncherModel.Callbacks.
3796 */
3797 public void bindFolders(final HashMap<Long, FolderInfo> folders) {
3798 Runnable r = new Runnable() {
3799 public void run() {
3800 bindFolders(folders);
3801 }
3802 };
3803 if (waitUntilResume(r)) {
3804 return;
3805 }
3806 sFolders.clear();
3807 sFolders.putAll(folders);
3808 }
3809
3810 /**
3811 * Add the views for a widget to the workspace.
3812 *
3813 * Implementation of the method from LauncherModel.Callbacks.
3814 */
3815 public void bindAppWidget(final LauncherAppWidgetInfo item) {
3816 Runnable r = new Runnable() {
3817 public void run() {
3818 bindAppWidget(item);
3819 }
3820 };
3821 if (waitUntilResume(r)) {
3822 return;
3823 }
3824 final long start = (DEBUG_WIDGETS) ? SystemClock.uptimeMillis() : 0;
3825 if (DEBUG_WIDGETS) {
3826 Log.d(TAG, "bindAppWidget: " + item);
3827 }
3828 final Workspace workspace = mWorkspace;
3829 LauncherAppWidgetProviderInfo appWidgetInfo = LauncherModel.getProviderInfo(this, item.providerNa🔵
3830 if (((!mIsSafeModeEnabled) && ((item.restoreStatus & LauncherAppWidgetInfo.FLAG_PROVIDER_NOT_READ🔵
3831 if (appWidgetInfo == null) {
3832 if (DEBUG_WIDGETS) {
3833 Log.d(TAG, ((("Removing restored widget: id=" + item.appWidgetId) + " belongs to comp🔵
3834 }
3835 LauncherModel.deleteItemFromDatabase(this, item);
3836 return;
3837 }
3838 // Note: This assumes that the id remap broadcast is received before this step.
3839 // If that is not the case, the id remap will be ignored and user may see the
3840 // click to setup view.
3841 PendingAddWidgetInfo pendingInfo = new PendingAddWidgetInfo(appWidgetInfo, null);
3842 pendingInfo.spanX = item.spanX;
3843 pendingInfo.spanY = item.spanY;
3844 pendingInfo.minSpanX = item.minSpanX;
3845 pendingInfo.minSpanY = item.minSpanY;
3846 Bundle options = null;
3847 // AppsCustomizePagedView.getDefaultOptionsForWidget(this, pendingInfo);
3848 int newWidgetId = mAppWidgetHost.allocateAppWidgetId();
3849 boolean success = mAppWidgetManager.bindAppWidgetIdIfAllowed(newWidgetId, appWidgetInfo, opti🔵
3850 // TODO consider showing a permission dialog when the widget is clicked.
3851 if (!success) {
3852 mAppWidgetHost.deleteAppWidgetId(newWidgetId);
3853 if (DEBUG_WIDGETS) {
3854 Log.d(TAG, ((("Removing restored widget: id=" + item.appWidgetId) + " belongs to comp🔵
3855 }
3856 LauncherModel.deleteItemFromDatabase(this, item);
3857 return;
3858 }
3859 item.appWidgetId = newWidgetId;
3860 // If the widget has a configure activity, it is still needs to set it up, otherwise
3861 // the widget is ready to go.
3862 item.restoreStatus = (appWidgetInfo.configure == null) ? LauncherAppWidgetInfo.RESTORE_COMPLE🔵
3863 LauncherModel.updateItemInDatabase(this, item);
3864 }
3865 if ((!mIsSafeModeEnabled) && (item.restoreStatus == LauncherAppWidgetInfo.RESTORE_COMPLETED)) {
3866 final int appWidgetId = item.appWidgetId;
3867 if (DEBUG_WIDGETS) {
3868 Log.d(TAG, (("bindAppWidget: id=" + item.appWidgetId) + " belongs to component ") + appWi🔵
3869 }
3870 item.hostView = mAppWidgetHost.createView(this, appWidgetId, appWidgetInfo);
3871 } else {
3872 appWidgetInfo = null;
3873 PendingAppWidgetHostView view = new PendingAppWidgetHostView(this, item, mIsSafeModeEnabled);
3874 view.updateIcon(mIconCache);
3875 item.hostView = view;
3876 item.hostView.updateAppWidget(null);
3877 item.hostView.setOnClickListener(this);
3878 }
3879 item.hostView.setTag(item);
3880 item.onBindAppWidget(this);
3881 workspace.addInScreen(item.hostView, item.container, item.screenId, item.cellX, item.cellY, item.🔵
3882 if (!item.isCustomWidget()) {
3883 addWidgetToAutoAdvanceIfNeeded(item.hostView, appWidgetInfo);
3884 }
3885 workspace.requestLayout();
3886 if (DEBUG_WIDGETS) {
3887 Log.d(TAG, ((("bound widget id=" + item.appWidgetId) + " in ") + (SystemClock.uptimeMillis() 🔵
3888 }
3889 }
3890
3891 /**
3892 * Restores a pending widget.
3893 *
3894 * @param appWidgetId The app widget id
3895 * @param cellInfo The position on screen where to create the widget.
3896 */
3897 private void completeRestoreAppWidget(final int appWidgetId) {
3898 LauncherAppWidgetHostView view = mWorkspace.getWidgetForAppWidgetId(appWidgetId);
3899 if ((view == null) || !(view instanceof PendingAppWidgetHostView)) {
3900 Log.e(TAG, "Widget update called, when the widget no longer exists.");
3901 return;
3902 }
3903
3904 LauncherAppWidgetInfo info = (LauncherAppWidgetInfo) view.getTag();
3905 info.restoreStatus = LauncherAppWidgetInfo.RESTORE_COMPLETED;
3906
3907 mWorkspace.reinflateWidgetsIfNecessary();
3908 LauncherModel.updateItemInDatabase(this, info);
3909 }
3910
3911 public void onPageBoundSynchronously(int page) {
3912 mSynchronouslyBoundPages.add(page);
3913 }
3914
3915 /**
3916 * Callback saying that there aren't any more items to bind.
3917 *
3918 * Implementation of the method from LauncherModel.Callbacks.
3919 */
3920 public void finishBindingItems() {
3921 Runnable r = new Runnable() {
3922 public void run() {
3923 finishBindingItems();
3924 }
3925 };
3926 if (waitUntilResume(r)) {
3927 return;
3928 }
3929 if (mSavedState != null) {
3930 if (!mWorkspace.hasFocus()) {
3931 mWorkspace.getChildAt(mWorkspace.getCurrentPage()).requestFocus();
3932 }
3933 mSavedState = null;
3934 }
3935 mWorkspace.restoreInstanceStateForRemainingPages();
3936 setWorkspaceLoading(false);
3937 sendLoadingCompleteBroadcastIfNecessary();
3938 // If we received the result of any pending adds while the loader was running (e.g. the
3939 // widget configuration forced an orientation change), process them now.
3940 if (sPendingAddItem != null) {
3941 final long screenId = completeAdd(sPendingAddItem);
3942 // TODO: this moves the user to the page where the pending item was added. Ideally,
3943 // the screen would be guaranteed to exist after bind, and the page would be set through
3944 // the workspace restore process.
3945 mWorkspace.post(new Runnable() {
3946 @Override
3947 public void run() {
3948 mWorkspace.snapToScreenId(screenId);
3949 }
3950 });
3951 sPendingAddItem = null;
3952 }
3953 PackageInstallerCompat.getInstance(this).onFinishBind();
3954 if (mLauncherCallbacks != null) {
3955 mLauncherCallbacks.finishBindingItems(false);
3956 }
3957 }
3958
3959 private void sendLoadingCompleteBroadcastIfNecessary() {
3960 if (!mSharedPrefs.getBoolean(FIRST_LOAD_COMPLETE, false)) {
3961 String permission =
3962 getResources().getString(R.string.receive_first_load_broadcast_permission);
3963 Intent intent = new Intent(ACTION_FIRST_LOAD_COMPLETE);
3964 sendBroadcast(intent, permission);
3965 SharedPreferences.Editor editor = mSharedPrefs.edit();
3966 editor.putBoolean(FIRST_LOAD_COMPLETE, true);
3967 editor.apply();
3968 }
3969 }
3970
3971 public boolean isAllAppsButtonRank(int rank) {
3972 if (mHotseat != null) {
3973 return mHotseat.isAllAppsButtonRank(rank);
3974 }
3975 return false;
3976 }
3977
3978 private boolean canRunNewAppsAnimation() {
3979 long diff = System.currentTimeMillis() - mDragController.getLastGestureUpTime();
3980 return diff > (NEW_APPS_ANIMATION_INACTIVE_TIMEOUT_SECONDS * 1000);
3981 }
3982
3983 private ValueAnimator createNewAppBounceAnimation(View v, int i) {
3984 ValueAnimator bounceAnim = LauncherAnimUtils.ofPropertyValuesHolder(v,
3985 PropertyValuesHolder.ofFloat("alpha", 1f),
3986 PropertyValuesHolder.ofFloat("scaleX", 1f),
3987 PropertyValuesHolder.ofFloat("scaleY", 1f));
3988 bounceAnim.setDuration(InstallShortcutReceiver.NEW_SHORTCUT_BOUNCE_DURATION);
3989 bounceAnim.setStartDelay(i * InstallShortcutReceiver.NEW_SHORTCUT_STAGGER_DELAY);
3990 bounceAnim.setInterpolator(new SmoothPagedView.OvershootInterpolator());
3991 return bounceAnim;
3992 }
3993
3994 public boolean useVerticalBarLayout() {
3995 return LauncherAppState.getInstance().getDynamicGrid().
3996 getDeviceProfile().isVerticalBarLayout();
3997 }
3998
3999 protected Rect getSearchBarBounds() {
4000 return LauncherAppState.getInstance().getDynamicGrid().
4001 getDeviceProfile().getSearchBarBounds();
4002 }
4003
4004 public void bindSearchablesChanged() {
4005 if (mSearchDropTargetBar == null) {
4006 return;
4007 }
4008 if (mQsb != null) {
4009 mSearchDropTargetBar.removeView(mQsb);
4010 mQsb = null;
4011 }
4012 mSearchDropTargetBar.setQsbSearchBar(getOrCreateQsbBar());
4013 }
4014
4015 /**
4016 * Add the icons for all apps.
4017 *
4018 * Implementation of the method from LauncherModel.Callbacks.
4019 */
4020 public void bindAllApplications(final ArrayList<AppInfo> apps) {
4021 if (mAppsView != null) {
4022 mAppsView.setApps(apps);
4023 }
4024 if (mWidgetsView != null) {
4025 mWidgetsView.addWidgets(LauncherModel.getSortedWidgetsAndShortcuts(this, false), getPackageMa🔵
4026 }
4027 if (mLauncherCallbacks != null) {
4028 mLauncherCallbacks.bindAllApplications(apps);
4029 }
4030 }
4031
4032 /**
4033 * A package was updated.
4034 *
4035 * Implementation of the method from LauncherModel.Callbacks.
4036 */
4037 public void bindAppsUpdated(final ArrayList<AppInfo> apps) {
4038 Runnable r = new Runnable() {
4039 public void run() {
4040 bindAppsUpdated(apps);
4041 }
4042 };
4043 if (waitUntilResume(r)) {
4044 return;
4045 }
4046 if (mAppsView != null) {
4047 mAppsView.updateApps(apps);
4048 }
4049 }
4050
4051 @Override
4052 public void bindWidgetsRestored(final ArrayList<LauncherAppWidgetInfo> widgets) {
4053 Runnable r = new Runnable() {
4054 public void run() {
4055 bindWidgetsRestored(widgets);
4056 }
4057 };
4058 if (waitUntilResume(r)) {
4059 return;
4060 }
4061 mWorkspace.widgetsRestored(widgets);
4062 }
4063
4064 /**
4065 * Some shortcuts were updated in the background.
4066 *
4067 * Implementation of the method from LauncherModel.Callbacks.
4068 */
4069 @Override
4070 public void bindShortcutsChanged(final ArrayList<ShortcutInfo> updated,
4071 final ArrayList<ShortcutInfo> removed, final UserHandleCompat user) {
4072 Runnable r = new Runnable() {
4073 public void run() {
4074 bindShortcutsChanged(updated, removed, user);
4075 }
4076 };
4077 if (waitUntilResume(r)) {
4078 return;
4079 }
4080
4081 if (!updated.isEmpty()) {
4082 mWorkspace.updateShortcuts(updated);
4083 }
4084
4085 if (!removed.isEmpty()) {
4086 HashSet<ComponentName> removedComponents = new HashSet<ComponentName>();
4087 for (ShortcutInfo si : removed) {
4088 removedComponents.add(si.getTargetComponent());
4089 }
4090 mWorkspace.removeItemsByComponentName(removedComponents, user);
4091 // Notify the drag controller
4092 mDragController.onAppsRemoved(new ArrayList<String>(), removedComponents);
4093 }
4094 }
4095
4096 /**
4097 * Update the state of a package, typically related to install state.
4098 *
4099 * Implementation of the method from LauncherModel.Callbacks.
4100 */
4101 @Override
4102 public void updatePackageState(ArrayList<PackageInstallInfo> installInfo) {
4103 if (mWorkspace != null) {
4104 mWorkspace.updatePackageState(installInfo);
4105 }
4106 }
4107
4108 /**
4109 * Update the label and icon of all the icons in a package
4110 *
4111 * Implementation of the method from LauncherModel.Callbacks.
4112 */
4113 @Override
4114 public void updatePackageBadge(String packageName) {
4115 if (mWorkspace != null) {
4116 mWorkspace.updatePackageBadge(packageName, UserHandleCompat.myUserHandle());
4117 }
4118 }
4119
4120 /**
4121 * A package was uninstalled. We take both the super set of packageNames
4122 * in addition to specific applications to remove, the reason being that
4123 * this can be called when a package is updated as well. In that scenario,
4124 * we only remove specific components from the workspace, where as
4125 * package-removal should clear all items by package name.
4126 *
4127 * @param reason if non-zero, the icons are not permanently removed, rather marked as disabled.
4128 * Implementation of the method from LauncherModel.Callbacks.
4129 */
4130 @Override
4131 public void bindComponentsRemoved(final ArrayList<String> packageNames, final ArrayList<AppInfo> appI🔵
4132 Runnable r = new Runnable() {
4133 public void run() {
4134 bindComponentsRemoved(packageNames, appInfos, user, reason);
4135 }
4136 };
4137 if (waitUntilResume(r)) {
4138 return;
4139 }
4140 if (reason == 0) {
4141 HashSet<ComponentName> removedComponents = new HashSet<ComponentName>();
4142 for (AppInfo info : appInfos) {
4143 removedComponents.add(info.componentName);
4144 }
4145 if (!packageNames.isEmpty()) {
4146 mWorkspace.removeItemsByPackageName(packageNames, user);
4147 }
4148 if (!removedComponents.isEmpty()) {
4149 mWorkspace.removeItemsByComponentName(removedComponents, user);
4150 }
4151 // Notify the drag controller
4152 mDragController.onAppsRemoved(packageNames, removedComponents);
4153 } else {
4154 mWorkspace.disableShortcutsByPackageName(packageNames, user, reason);
4155 }
4156 // Update AllApps
4157 if (mAppsView != null) {
4158 mAppsView.removeApps(appInfos);
4159 }
4160 }
4161
4162 /**
4163 * A number of packages were updated.
4164 */
4165 @Thunk
4166 ArrayList<Object> mWidgetsAndShortcuts;
4167
4168 private Runnable mBindPackagesUpdatedRunnable = new Runnable() {
4169 public void run() {
4170 bindPackagesUpdated(mWidgetsAndShortcuts);
4171 mWidgetsAndShortcuts = null;
4172 }
4173 };
4174
4175 public void bindPackagesUpdated(final ArrayList<Object> widgetsAndShortcuts) {
4176 if (waitUntilResume(mBindPackagesUpdatedRunnable, true)) {
4177 mWidgetsAndShortcuts = widgetsAndShortcuts;
4178 return;
4179 }
4180 if (mWidgetsView != null) {
4181 mWidgetsView.addWidgets(LauncherModel.getSortedWidgetsAndShortcuts(this, false), getPackageMa🔵
4182 }
4183 }
4184
4185 private int mapConfigurationOriActivityInfoOri(int configOri) {
4186 final Display d = getWindowManager().getDefaultDisplay();
4187 int naturalOri = Configuration.ORIENTATION_LANDSCAPE;
4188 switch (d.getRotation()) {
4189 case Surface.ROTATION_0:
4190 case Surface.ROTATION_180:
4191 // We are currently in the same basic orientation as the natural orientation
4192 naturalOri = configOri;
4193 break;
4194 case Surface.ROTATION_90:
4195 case Surface.ROTATION_270:
4196 // We are currently in the other basic orientation to the natural orientation
4197 naturalOri = (configOri == Configuration.ORIENTATION_LANDSCAPE) ?
4198 Configuration.ORIENTATION_PORTRAIT : Configuration.ORIENTATION_LANDSCAPE;
4199 break;
4200 }
4201
4202 int[] oriMap = {
4203 ActivityInfo.SCREEN_ORIENTATION_PORTRAIT,
4204 ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE,
4205 ActivityInfo.SCREEN_ORIENTATION_REVERSE_PORTRAIT,
4206 ActivityInfo.SCREEN_ORIENTATION_REVERSE_LANDSCAPE
4207 };
4208 // Since the map starts at portrait, we need to offset if this device's natural orientation
4209 // is landscape.
4210 int indexOffset = 0;
4211 if (naturalOri == Configuration.ORIENTATION_LANDSCAPE) {
4212 indexOffset = 1;
4213 }
4214 return oriMap[(d.getRotation() + indexOffset) % 4];
4215 }
4216
4217 public void lockScreenOrientation() {
4218 if (Utilities.isRotationEnabled(this)) {
4219 if (Build.VERSION.SDK_INT < Build.VERSION_CODES.JELLY_BEAN_MR2) {
4220 setRequestedOrientation(mapConfigurationOriActivityInfoOri(getResources()
4221 .getConfiguration().orientation));
4222 } else {
4223 setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_LOCKED);
4224 }
4225 }
4226 }
4227
4228 public void unlockScreenOrientation(boolean immediate) {
4229 if (Utilities.isRotationEnabled(this)) {
4230 if (immediate) {
4231 setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED);
4232 } else {
4233 mHandler.postDelayed(new Runnable() {
4234 public void run() {
4235 setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED);
4236 }
4237 }, mRestoreScreenOrientationDelay);
4238 }
4239 }
4240 }
4241
4242 protected boolean isLauncherPreinstalled() {
4243 if (mLauncherCallbacks != null) {
4244 return mLauncherCallbacks.isLauncherPreinstalled();
4245 }
4246 PackageManager pm = getPackageManager();
4247 try {
4248 ApplicationInfo ai = pm.getApplicationInfo(getComponentName().getPackageName(), 0);
4249 if ((ai.flags & ApplicationInfo.FLAG_SYSTEM) != 0) {
4250 return true;
4251 } else {
4252 return false;
4253 }
4254 } catch (NameNotFoundException e) {
4255 e.printStackTrace();
4256 return false;
4257 }
4258 }
4259
4260 /**
4261 * This method indicates whether or not we should suggest default wallpaper dimensions
4262 * when our wallpaper cropper was not yet used to set a wallpaper.
4263 */
4264 protected boolean overrideWallpaperDimensions() {
4265 if (mLauncherCallbacks != null) {
4266 return mLauncherCallbacks.overrideWallpaperDimensions();
4267 }
4268 return true;
4269 }
4270
4271 /**
4272 * To be overridden by subclasses to indicate that there is an activity to launch
4273 * before showing the standard launcher experience.
4274 */
4275 protected boolean hasFirstRunActivity() {
4276 if (mLauncherCallbacks != null) {
4277 return mLauncherCallbacks.hasFirstRunActivity();
4278 }
4279 return false;
4280 }
4281
4282 /**
4283 * To be overridden by subclasses to launch any first run activity
4284 */
4285 protected Intent getFirstRunActivity() {
4286 if (mLauncherCallbacks != null) {
4287 return mLauncherCallbacks.getFirstRunActivity();
4288 }
4289 return null;
4290 }
4291
4292 /**
4293 * Returns whether the launcher callbacks overrides search in all apps.
4294 * @return
4295 */
4296 @Thunk
4297 boolean isAllAppsSearchOverridden() {
4298 if (mLauncherCallbacks != null) {
4299 return mLauncherCallbacks.overrideAllAppsSearch();
4300 }
4301 return false;
4302 }
4303
4304 private boolean shouldRunFirstRunActivity() {
4305 return !ActivityManager.isRunningInTestHarness() &&
4306 !mSharedPrefs.getBoolean(FIRST_RUN_ACTIVITY_DISPLAYED, false);
4307 }
4308
4309 protected boolean hasRunFirstRunActivity() {
4310 return mSharedPrefs.getBoolean(FIRST_RUN_ACTIVITY_DISPLAYED, false);
4311 }
4312
4313 public boolean showFirstRunActivity() {
4314 if (shouldRunFirstRunActivity() &&
4315 hasFirstRunActivity()) {
4316 Intent firstRunIntent = getFirstRunActivity();
4317 if (firstRunIntent != null) {
4318 startActivity(firstRunIntent);
4319 markFirstRunActivityShown();
4320 return true;
4321 }
4322 }
4323 return false;
4324 }
4325
4326 private void markFirstRunActivityShown() {
4327 SharedPreferences.Editor editor = mSharedPrefs.edit();
4328 editor.putBoolean(FIRST_RUN_ACTIVITY_DISPLAYED, true);
4329 editor.apply();
4330 }
4331
4332 /**
4333 * To be overridden by subclasses to indicate that there is an in-activity full-screen intro
4334 * screen that must be displayed and dismissed.
4335 */
4336 protected boolean hasDismissableIntroScreen() {
4337 if (mLauncherCallbacks != null) {
4338 return mLauncherCallbacks.hasDismissableIntroScreen();
4339 }
4340 return false;
4341 }
4342
4343 /**
4344 * Full screen intro screen to be shown and dismissed before the launcher can be used.
4345 */
4346 protected View getIntroScreen() {
4347 if (mLauncherCallbacks != null) {
4348 return mLauncherCallbacks.getIntroScreen();
4349 }
4350 return null;
4351 }
4352
4353 /**
4354 * To be overriden by subclasses to indicate whether the in-activity intro screen has been
4355 * dismissed. This method is ignored if #hasDismissableIntroScreen returns false.
4356 */
4357 private boolean shouldShowIntroScreen() {
4358 return hasDismissableIntroScreen() &&
4359 !mSharedPrefs.getBoolean(INTRO_SCREEN_DISMISSED, false);
4360 }
4361
4362 protected void showIntroScreen() {
4363 View introScreen = getIntroScreen();
4364 changeWallpaperVisiblity(false);
4365 if (introScreen != null) {
4366 mDragLayer.showOverlayView(introScreen);
4367 }
4368 if (mLauncherOverlayContainer != null) {
4369 mLauncherOverlayContainer.setVisibility(View.INVISIBLE);
4370 }
4371 }
4372
4373 public void dismissIntroScreen() {
4374 markIntroScreenDismissed();
4375 if (showFirstRunActivity()) {
4376 // We delay hiding the intro view until the first run activity is showing. This
4377 // avoids a blip.
4378 mWorkspace.postDelayed(new Runnable() {
4379 @Override
4380 public void run() {
4381 mDragLayer.dismissOverlayView();
4382 if (mLauncherOverlayContainer != null) {
4383 mLauncherOverlayContainer.setVisibility(View.VISIBLE);
4384 }
4385 showFirstRunClings();
4386 }
4387 }, ACTIVITY_START_DELAY);
4388 } else {
4389 mDragLayer.dismissOverlayView();
4390 if (mLauncherOverlayContainer != null) {
4391 mLauncherOverlayContainer.setVisibility(View.VISIBLE);
4392 }
4393 showFirstRunClings();
4394 }
4395 changeWallpaperVisiblity(true);
4396 }
4397
4398 private void markIntroScreenDismissed() {
4399 SharedPreferences.Editor editor = mSharedPrefs.edit();
4400 editor.putBoolean(INTRO_SCREEN_DISMISSED, true);
4401 editor.apply();
4402 }
4403
4404 @Thunk
4405 void showFirstRunClings() {
4406 // The two first run cling paths are mutually exclusive, if the launcher is preinstalled
4407 // on the device, then we always show the first run cling experience (or if there is no
4408 // launcher2). Otherwise, we prompt the user upon started for migration
4409 LauncherClings launcherClings = new LauncherClings(this);
4410 if (launcherClings.shouldShowFirstRunOrMigrationClings()) {
4411 if (mModel.canMigrateFromOldLauncherDb(this)) {
4412 launcherClings.showMigrationCling();
4413 } else {
4414 launcherClings.showLongPressCling(true);
4415 }
4416 }
4417 }
4418
4419 void showWorkspaceSearchAndHotseat() {
4420 if (mWorkspace != null) mWorkspace.setAlpha(1f);
4421 if (mHotseat != null) mHotseat.setAlpha(1f);
4422 if (mPageIndicators != null) mPageIndicators.setAlpha(1f);
4423 if (mSearchDropTargetBar != null) mSearchDropTargetBar.showSearchBar(false);
4424 }
4425
4426 void hideWorkspaceSearchAndHotseat() {
4427 if (mWorkspace != null) mWorkspace.setAlpha(0f);
4428 if (mHotseat != null) mHotseat.setAlpha(0f);
4429 if (mPageIndicators != null) mPageIndicators.setAlpha(0f);
4430 if (mSearchDropTargetBar != null) mSearchDropTargetBar.hideSearchBar(false);
4431 }
4432
4433 public ItemInfo createAppDragInfo(Intent appLaunchIntent) {
4434 // Called from search suggestion, not supported in other profiles.
4435 final UserHandleCompat myUser = UserHandleCompat.myUserHandle();
4436 LauncherAppsCompat launcherApps = LauncherAppsCompat.getInstance(this);
4437 LauncherActivityInfoCompat activityInfo = launcherApps.resolveActivity(appLaunchIntent, myUser);
4438 if (activityInfo == null) {
4439 return null;
4440 }
4441 return new AppInfo(this, activityInfo, myUser, mIconCache);
4442 }
4443
4444 public ItemInfo createShortcutDragInfo(Intent shortcutIntent, CharSequence caption,
4445 Bitmap icon) {
4446 // Called from search suggestion, not supported in other profiles.
4447 return createShortcutDragInfo(shortcutIntent, caption, icon,
4448 UserHandleCompat.myUserHandle());
4449 }
4450
4451 public ItemInfo createShortcutDragInfo(Intent shortcutIntent, CharSequence caption,
4452 Bitmap icon, UserHandleCompat user) {
4453 UserManagerCompat userManager = UserManagerCompat.getInstance(this);
4454 CharSequence contentDescription = userManager.getBadgedLabelForUser(caption, user);
4455 return new ShortcutInfo(shortcutIntent, caption, contentDescription, icon, user);
4456 }
4457
4458 protected void moveWorkspaceToDefaultScreen() {
4459 mWorkspace.moveToDefaultScreen(false);
4460 }
4461
4462 public void startDrag(View dragView, ItemInfo dragInfo, DragSource source) {
4463 dragView.setTag(dragInfo);
4464 mWorkspace.onExternalDragStartedWithItem(dragView);
4465 mWorkspace.beginExternalDragShared(dragView, source);
4466 }
4467
4468 @Override
4469 public void onPageSwitch(View newPage, int newPageIndex) {
4470 if (mLauncherCallbacks != null) {
4471 mLauncherCallbacks.onPageSwitch(newPage, newPageIndex);
4472 }
4473 }
4474
4475 /**
4476 * Prints out out state for debugging.
4477 */
4478 public void dumpState() {
4479 Log.d(TAG, "BEGIN launcher3 dump state for launcher " + this);
4480 Log.d(TAG, "mSavedState=" + mSavedState);
4481 Log.d(TAG, "mWorkspaceLoading=" + mWorkspaceLoading);
4482 Log.d(TAG, "mRestoring=" + mRestoring);
4483 Log.d(TAG, "mWaitingForResult=" + mWaitingForResult);
4484 Log.d(TAG, "mSavedInstanceState=" + mSavedInstanceState);
4485 Log.d(TAG, "sFolders.size=" + sFolders.size());
4486 mModel.dumpState();
4487 // TODO(hyunyoungs): add mWidgetsView.dumpState(); or mWidgetsModel.dumpState();
4488 Log.d(TAG, "END launcher3 dump state");
4489 }
4490
4491 @Override
4492 public void dump(String prefix, FileDescriptor fd, PrintWriter writer, String[] args) {
4493 super.dump(prefix, fd, writer, args);
4494 synchronized (sDumpLogs) {
4495 writer.println(" ");
4496 writer.println("Debug logs: ");
4497 for (int i = 0; i < sDumpLogs.size(); i++) {
4498 writer.println(" " + sDumpLogs.get(i));
4499 }
4500 }
4501 if (mLauncherCallbacks != null) {
4502 mLauncherCallbacks.dump(prefix, fd, writer, args);
4503 }
4504 }
4505
4506 public static void dumpDebugLogsToConsole() {
4507 if (DEBUG_DUMP_LOG) {
4508 synchronized (sDumpLogs) {
4509 Log.d(TAG, "");
4510 Log.d(TAG, "*********************");
4511 Log.d(TAG, "Launcher debug logs: ");
4512 for (int i = 0; i < sDumpLogs.size(); i++) {
4513 Log.d(TAG, " " + sDumpLogs.get(i));
4514 }
4515 Log.d(TAG, "*********************");
4516 Log.d(TAG, "");
4517 }
4518 }
4519 }
4520
4521 public static void addDumpLog(String tag, String log, boolean debugLog) {
4522 addDumpLog(tag, log, null, debugLog);
4523 }
4524
4525 public static void addDumpLog(String tag, String log, Exception e, boolean debugLog) {
4526 if (debugLog) {
4527 if (e != null) {
4528 Log.d(tag, log, e);
4529 } else {
4530 Log.d(tag, log);
4531 }
4532 }
4533 if (DEBUG_DUMP_LOG) {
4534 sDateStamp.setTime(System.currentTimeMillis());
4535 synchronized (sDumpLogs) {
4536 sDumpLogs.add(sDateFormat.format(sDateStamp) + ": " + tag + ", " + log
4537 + (e == null ? "" : (", Exception: " + e)));
4538 }
4539 }
4540 }
4541
4542 public static CustomAppWidget getCustomAppWidget(String name) {
4543 return sCustomAppWidgets.get(name);
4544 }
4545
4546 public static HashMap<String, CustomAppWidget> getCustomAppWidgets() {
4547 return sCustomAppWidgets;
4548 }
4549
4550 public void dumpLogsToLocalData() {
4551 if (DEBUG_DUMP_LOG) {
4552 new AsyncTask<Void, Void, Void>() {
4553 public Void doInBackground(Void ... args) {
4554 boolean success = false;
4555 sDateStamp.setTime(sRunStart);
4556 String FILENAME = sDateStamp.getMonth() + "-"
4557 + sDateStamp.getDay() + "_"
4558 + sDateStamp.getHours() + "-"
4559 + sDateStamp.getMinutes() + "_"
4560 + sDateStamp.getSeconds() + ".txt";
4561
4562 FileOutputStream fos = null;
4563 File outFile = null;
4564 try {
4565 outFile = new File(getFilesDir(), FILENAME);
4566 outFile.createNewFile();
4567 fos = new FileOutputStream(outFile);
4568 } catch (Exception e) {
4569 e.printStackTrace();
4570 }
4571 if (fos != null) {
4572 PrintWriter writer = new PrintWriter(fos);
4573
4574 writer.println(" ");
4575 writer.println("Debug logs: ");
4576 synchronized (sDumpLogs) {
4577 for (int i = 0; i < sDumpLogs.size(); i++) {
4578 writer.println(" " + sDumpLogs.get(i));
4579 }
4580 }
4581 writer.close();
4582 }
4583 try {
4584 if (fos != null) {
4585 fos.close();
4586 success = true;
4587 }
4588 } catch (IOException e) {
4589 e.printStackTrace();
4590 }
4591 return null;
4592 }
4593 }.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR, (Void) null);
4594 }
4595 }
4596 }
4597
4598 interface LauncherTransitionable {
4599 public abstract View getContent();
4600
4601 public abstract void onLauncherTransitionPrepare(Launcher l, boolean animated, boolean toWorkspace);
4602
4603 public abstract void onLauncherTransitionStart(Launcher l, boolean animated, boolean toWorkspace);
4604
4605 public abstract void onLauncherTransitionStep(Launcher l, float t);
4606
4607 public abstract void onLauncherTransitionEnd(Launcher l, boolean animated, boolean toWorkspace);
4608 }
|